feat: Added SocialIcons component with 9 platform SVG icons, rendered s…
- "frontend/src/components/SocialIcons.tsx" - "frontend/src/pages/CreatorDetail.tsx" - "frontend/src/App.css" GSD-Task: S02/T02
This commit is contained in:
parent
cafbd0afb1
commit
0234a87429
7 changed files with 264 additions and 3 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
24
.gsd/milestones/M017/slices/S02/tasks/T01-VERIFY.json
Normal file
24
.gsd/milestones/M017/slices/S02/tasks/T01-VERIFY.json
Normal file
|
|
@ -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
|
||||
}
|
||||
80
.gsd/milestones/M017/slices/S02/tasks/T02-SUMMARY.md
Normal file
80
.gsd/milestones/M017/slices/S02/tasks/T02-SUMMARY.md
Normal file
|
|
@ -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.
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
110
frontend/src/components/SocialIcons.tsx
Normal file
110
frontend/src/components/SocialIcons.tsx
Normal file
|
|
@ -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 (
|
||||
<svg viewBox="0 0 24 24" style={S}>
|
||||
<rect {...P} x="2" y="2" width="20" height="20" rx="5" />
|
||||
<circle {...P} cx="12" cy="12" r="5" />
|
||||
<circle fill="currentColor" stroke="none" cx="17.5" cy="6.5" r="1.2" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function IconYoutube() {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" style={S}>
|
||||
<rect {...P} x="2" y="4" width="20" height="16" rx="4" />
|
||||
<polygon {...P} points="10,8.5 16,12 10,15.5" fill="currentColor" stroke="none" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function IconBandcamp() {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" style={S}>
|
||||
<polygon {...P} points="6,16 10,8 18,8 14,16" strokeWidth={1.8} />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function IconSoundcloud() {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" style={S}>
|
||||
<path {...P} d="M2 16 v-3 M5 16 v-5 M8 16 v-7 M11 16 v-8 M14 16 v-6" strokeWidth={1.8} />
|
||||
<path {...P} d="M16 16 v-7 a4 4 0 0 1 4 0 v7" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function IconTwitter() {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" style={S}>
|
||||
<path {...P} d="M4 20 L10.5 12 M13.5 12 L20 4" strokeWidth={1.8} />
|
||||
<path {...P} d="M20 20 L13.5 12 M10.5 12 L4 4" strokeWidth={1.8} />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function IconSpotify() {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" style={S}>
|
||||
<circle {...P} cx="12" cy="12" r="10" />
|
||||
<path {...P} d="M7 9.5 q5-2 10 0" />
|
||||
<path {...P} d="M8 12.5 q4-1.5 8 0" />
|
||||
<path {...P} d="M9 15.5 q3-1 6 0" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function IconFacebook() {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" style={S}>
|
||||
<rect {...P} x="2" y="2" width="20" height="20" rx="5" />
|
||||
<path {...P} d="M15 3 v4 h-2 a1 1 0 0 0-1 1 v3 h3 l-0.5 3 H12 v7" strokeWidth={1.8} />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function IconTwitch() {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" style={S}>
|
||||
<path {...P} d="M4 3 L4 19 H8 V22 L11 19 H15 L20 14 V3 Z" />
|
||||
<line {...P} x1="10" y1="8" x2="10" y2="12" />
|
||||
<line {...P} x1="15" y1="8" x2="15" y2="12" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function IconGlobe() {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" style={S}>
|
||||
<circle {...P} cx="12" cy="12" r="10" />
|
||||
<ellipse {...P} cx="12" cy="12" rx="4" ry="10" />
|
||||
<line {...P} x1="2" y1="12" x2="22" y2="12" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
const ICON_MAP: Record<string, () => 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 <Icon />;
|
||||
}
|
||||
|
|
@ -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 && (
|
||||
<p className="creator-hero__bio">{creator.bio}</p>
|
||||
)}
|
||||
{creator.social_links && Object.keys(creator.social_links).length > 0 && (
|
||||
<div className="creator-hero__socials">
|
||||
{Object.entries(creator.social_links).map(([platform, url]) => (
|
||||
<a key={platform} href={url} target="_blank" rel="noopener noreferrer"
|
||||
className="creator-hero__social-link" title={platform}>
|
||||
<SocialIcon platform={platform} />
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{creator.genres && creator.genres.length > 0 && (
|
||||
<div className="creator-hero__genres">
|
||||
{creator.genres.map((g) => (
|
||||
|
|
@ -134,9 +145,17 @@ export default function CreatorDetail() {
|
|||
|
||||
{/* Stats */}
|
||||
<div className="creator-detail__stats-bar">
|
||||
<span className="creator-detail__stats">
|
||||
{creator.technique_count} technique{creator.technique_count !== 1 ? "s" : ""}
|
||||
</span>
|
||||
<span className="creator-detail__stats-sep">·</span>
|
||||
<span className="creator-detail__stats">
|
||||
{creator.video_count} video{creator.video_count !== 1 ? "s" : ""}
|
||||
</span>
|
||||
<span className="creator-detail__stats-sep">·</span>
|
||||
<span className="creator-detail__stats">
|
||||
{creator.moment_count} moment{creator.moment_count !== 1 ? "s" : ""}
|
||||
</span>
|
||||
{Object.keys(creator.genre_breakdown).length > 0 && (
|
||||
<span className="creator-detail__topic-pills">
|
||||
{Object.entries(creator.genre_breakdown)
|
||||
|
|
|
|||
|
|
@ -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"}
|
||||
{"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"}
|
||||
Loading…
Add table
Reference in a new issue