From f4f21b6c369a5f170de4de0c56faad40dbd0170b Mon Sep 17 00:00:00 2001 From: jlightner Date: Fri, 3 Apr 2026 09:00:34 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20Added=20SocialIcons=20component=20with?= =?UTF-8?q?=209=20platform=20SVG=20icons,=20rendered=20s=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - "frontend/src/components/SocialIcons.tsx" - "frontend/src/pages/CreatorDetail.tsx" - "frontend/src/App.css" GSD-Task: S02/T02 --- frontend/src/App.css | 30 ++++++- frontend/src/components/SocialIcons.tsx | 110 ++++++++++++++++++++++++ frontend/src/pages/CreatorDetail.tsx | 19 ++++ frontend/tsconfig.app.tsbuildinfo | 2 +- 4 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 frontend/src/components/SocialIcons.tsx 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