diff --git a/.gsd/milestones/M017/slices/S02/S02-PLAN.md b/.gsd/milestones/M017/slices/S02/S02-PLAN.md index 85db5fe..2e1a8a1 100644 --- a/.gsd/milestones/M017/slices/S02/S02-PLAN.md +++ b/.gsd/milestones/M017/slices/S02/S02-PLAN.md @@ -31,7 +31,7 @@ - Estimate: 15m - Files: backend/schemas.py, backend/routers/creators.py, frontend/src/api/public-client.ts - Verify: cd frontend && npx tsc --noEmit -- [ ] **T02: Render social link icons in hero and enhance stats bar with all counts** — Create a SocialIcons component with inline SVGs for common platforms, render social links as clickable icons in the hero section, and update the stats bar to show technique/video/moment counts. +- [x] **T02: Added SocialIcons component with 9 platform SVG icons, rendered social links in creator hero, and expanded stats bar to show technique/video/moment counts** — Create a SocialIcons component with inline SVGs for common platforms, render social links as clickable icons in the hero section, and update the stats bar to show technique/video/moment counts. ## Steps diff --git a/.gsd/milestones/M017/slices/S02/tasks/T01-VERIFY.json b/.gsd/milestones/M017/slices/S02/tasks/T01-VERIFY.json new file mode 100644 index 0000000..c8c7fab --- /dev/null +++ b/.gsd/milestones/M017/slices/S02/tasks/T01-VERIFY.json @@ -0,0 +1,24 @@ +{ + "schemaVersion": 1, + "taskId": "T01", + "unitId": "M017/S02/T01", + "timestamp": 1775206685201, + "passed": false, + "discoverySource": "task-plan", + "checks": [ + { + "command": "cd frontend", + "exitCode": 0, + "durationMs": 6, + "verdict": "pass" + }, + { + "command": "npx tsc --noEmit", + "exitCode": 1, + "durationMs": 931, + "verdict": "fail" + } + ], + "retryAttempt": 1, + "maxRetries": 2 +} diff --git a/.gsd/milestones/M017/slices/S02/tasks/T02-SUMMARY.md b/.gsd/milestones/M017/slices/S02/tasks/T02-SUMMARY.md new file mode 100644 index 0000000..aaf604e --- /dev/null +++ b/.gsd/milestones/M017/slices/S02/tasks/T02-SUMMARY.md @@ -0,0 +1,80 @@ +--- +id: T02 +parent: S02 +milestone: M017 +provides: [] +requires: [] +affects: [] +key_files: ["frontend/src/components/SocialIcons.tsx", "frontend/src/pages/CreatorDetail.tsx", "frontend/src/App.css"] +key_decisions: ["Used X icon for twitter/x — both keys map to same crossed-lines SVG", "Added Twitch as 9th platform since it's common in music production community"] +patterns_established: [] +drill_down_paths: [] +observability_surfaces: [] +duration: "" +verification_result: "TypeScript compilation (npx tsc --noEmit) passed with exit 0. Production build (npm run build) passed with exit 0, producing 59 modules bundled successfully." +completed_at: 2026-04-03T09:00:24.834Z +blocker_discovered: false +--- + +# T02: Added SocialIcons component with 9 platform SVG icons, rendered social links in creator hero, and expanded stats bar to show technique/video/moment counts + +> Added SocialIcons component with 9 platform SVG icons, rendered social links in creator hero, and expanded stats bar to show technique/video/moment counts + +## What Happened +--- +id: T02 +parent: S02 +milestone: M017 +key_files: + - frontend/src/components/SocialIcons.tsx + - frontend/src/pages/CreatorDetail.tsx + - frontend/src/App.css +key_decisions: + - Used X icon for twitter/x — both keys map to same crossed-lines SVG + - Added Twitch as 9th platform since it's common in music production community +duration: "" +verification_result: passed +completed_at: 2026-04-03T09:00:24.834Z +blocker_discovered: false +--- + +# T02: Added SocialIcons component with 9 platform SVG icons, rendered social links in creator hero, and expanded stats bar to show technique/video/moment counts + +**Added SocialIcons component with 9 platform SVG icons, rendered social links in creator hero, and expanded stats bar to show technique/video/moment counts** + +## What Happened + +Created SocialIcons.tsx with inline SVGs for 9 platforms (Instagram, YouTube, Bandcamp, SoundCloud, Twitter/X, Spotify, Facebook, Twitch) plus a globe fallback. Updated CreatorDetail.tsx to render social links as clickable icons in the hero section and expanded the stats bar to show technique, video, and moment counts with dot separators. Added CSS styles for social link layout and hover states. + +## Verification + +TypeScript compilation (npx tsc --noEmit) passed with exit 0. Production build (npm run build) passed with exit 0, producing 59 modules bundled successfully. + +## Verification Evidence + +| # | Command | Exit Code | Verdict | Duration | +|---|---------|-----------|---------|----------| +| 1 | `cd frontend && npx tsc --noEmit` | 0 | ✅ pass | 3100ms | +| 2 | `cd frontend && npm run build` | 0 | ✅ pass | 6500ms | + + +## Deviations + +Added Twitch as 9th platform icon beyond the 8 specified in the plan. + +## Known Issues + +None. + +## Files Created/Modified + +- `frontend/src/components/SocialIcons.tsx` +- `frontend/src/pages/CreatorDetail.tsx` +- `frontend/src/App.css` + + +## Deviations +Added Twitch as 9th platform icon beyond the 8 specified in the plan. + +## Known Issues +None. diff --git a/frontend/src/App.css b/frontend/src/App.css index 1d95c39..6fb5bfb 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -2698,14 +2698,42 @@ a.app-footer__repo:hover { gap: 0.375rem; } +.creator-hero__socials { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin: 0.5rem 0; +} + +.creator-hero__social-link { + color: var(--color-text-muted); + transition: color 0.15s ease; + display: inline-flex; + align-items: center; +} + +.creator-hero__social-link:hover { + color: var(--color-accent); +} + +.creator-hero__social-link svg { + width: 1.25rem; + height: 1.25rem; +} + .creator-detail__stats-bar { display: flex; align-items: center; - gap: 0.75rem; + gap: 0.5rem; flex-wrap: wrap; margin-bottom: 0.5rem; } +.creator-detail__stats-sep { + color: var(--color-text-muted); + margin: 0 0.125rem; +} + .creator-detail__stats { font-size: 0.875rem; color: var(--color-text-secondary); diff --git a/frontend/src/components/SocialIcons.tsx b/frontend/src/components/SocialIcons.tsx new file mode 100644 index 0000000..c9c8435 --- /dev/null +++ b/frontend/src/components/SocialIcons.tsx @@ -0,0 +1,110 @@ +/** + * Inline SVG icons for social media platforms. + * Monoline stroke style matching CategoryIcons.tsx. + */ + +const S = { width: "1.25em", height: "1.25em", verticalAlign: "-0.15em" } as const; +const P = { fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round" as const, strokeLinejoin: "round" as const }; + +export function IconInstagram() { + return ( + + + + + + ); +} + +export function IconYoutube() { + return ( + + + + + ); +} + +export function IconBandcamp() { + return ( + + + + ); +} + +export function IconSoundcloud() { + return ( + + + + + ); +} + +export function IconTwitter() { + return ( + + + + + ); +} + +export function IconSpotify() { + return ( + + + + + + + ); +} + +export function IconFacebook() { + return ( + + + + + ); +} + +export function IconTwitch() { + return ( + + + + + + ); +} + +export function IconGlobe() { + return ( + + + + + + ); +} + +const ICON_MAP: Record JSX.Element> = { + instagram: IconInstagram, + youtube: IconYoutube, + bandcamp: IconBandcamp, + soundcloud: IconSoundcloud, + twitter: IconTwitter, + x: IconTwitter, + spotify: IconSpotify, + facebook: IconFacebook, + twitch: IconTwitch, +}; + +/** Resolves a platform name to the matching icon, falling back to globe. */ +export function SocialIcon({ platform }: { platform: string }) { + const Icon = ICON_MAP[platform.toLowerCase()] ?? IconGlobe; + return ; +} diff --git a/frontend/src/pages/CreatorDetail.tsx b/frontend/src/pages/CreatorDetail.tsx index 6388d8e..b58a978 100644 --- a/frontend/src/pages/CreatorDetail.tsx +++ b/frontend/src/pages/CreatorDetail.tsx @@ -12,6 +12,7 @@ import { type CreatorDetailResponse, } from "../api/public-client"; import CreatorAvatar from "../components/CreatorAvatar"; +import { SocialIcon } from "../components/SocialIcons"; import SortDropdown from "../components/SortDropdown"; import { catSlug } from "../utils/catSlug"; import { useDocumentTitle } from "../hooks/useDocumentTitle"; @@ -122,6 +123,16 @@ export default function CreatorDetail() { {creator.bio && ( {creator.bio} )} + {creator.social_links && Object.keys(creator.social_links).length > 0 && ( + + {Object.entries(creator.social_links).map(([platform, url]) => ( + + + + ))} + + )} {creator.genres && creator.genres.length > 0 && ( {creator.genres.map((g) => ( @@ -134,9 +145,17 @@ export default function CreatorDetail() { {/* Stats */} + + {creator.technique_count} technique{creator.technique_count !== 1 ? "s" : ""} + + · {creator.video_count} video{creator.video_count !== 1 ? "s" : ""} + · + + {creator.moment_count} moment{creator.moment_count !== 1 ? "s" : ""} + {Object.keys(creator.genre_breakdown).length > 0 && ( {Object.entries(creator.genre_breakdown) diff --git a/frontend/tsconfig.app.tsbuildinfo b/frontend/tsconfig.app.tsbuildinfo index a04a926..db3a8d3 100644 --- a/frontend/tsconfig.app.tsbuildinfo +++ b/frontend/tsconfig.app.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/api/public-client.ts","./src/components/AdminDropdown.tsx","./src/components/AppFooter.tsx","./src/components/CategoryIcons.tsx","./src/components/CopyLinkButton.tsx","./src/components/CreatorAvatar.tsx","./src/components/ReportIssueModal.tsx","./src/components/SearchAutocomplete.tsx","./src/components/SortDropdown.tsx","./src/components/TableOfContents.tsx","./src/components/TagList.tsx","./src/hooks/useCountUp.ts","./src/hooks/useDocumentTitle.ts","./src/hooks/useSortPreference.ts","./src/pages/About.tsx","./src/pages/AdminPipeline.tsx","./src/pages/AdminReports.tsx","./src/pages/AdminTechniquePages.tsx","./src/pages/CreatorDetail.tsx","./src/pages/CreatorsBrowse.tsx","./src/pages/Home.tsx","./src/pages/SearchResults.tsx","./src/pages/SubTopicPage.tsx","./src/pages/TechniquePage.tsx","./src/pages/TopicsBrowse.tsx","./src/utils/catSlug.ts","./src/utils/citations.tsx"],"version":"5.6.3"} \ No newline at end of file +{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/api/public-client.ts","./src/components/AdminDropdown.tsx","./src/components/AppFooter.tsx","./src/components/CategoryIcons.tsx","./src/components/CopyLinkButton.tsx","./src/components/CreatorAvatar.tsx","./src/components/ReportIssueModal.tsx","./src/components/SearchAutocomplete.tsx","./src/components/SocialIcons.tsx","./src/components/SortDropdown.tsx","./src/components/TableOfContents.tsx","./src/components/TagList.tsx","./src/hooks/useCountUp.ts","./src/hooks/useDocumentTitle.ts","./src/hooks/useSortPreference.ts","./src/pages/About.tsx","./src/pages/AdminPipeline.tsx","./src/pages/AdminReports.tsx","./src/pages/AdminTechniquePages.tsx","./src/pages/CreatorDetail.tsx","./src/pages/CreatorsBrowse.tsx","./src/pages/Home.tsx","./src/pages/SearchResults.tsx","./src/pages/SubTopicPage.tsx","./src/pages/TechniquePage.tsx","./src/pages/TopicsBrowse.tsx","./src/utils/catSlug.ts","./src/utils/citations.tsx"],"version":"5.6.3"} \ No newline at end of file
{creator.bio}