feat: Featured technique card with gradient border and enriched recent-…
- "frontend/src/api/public-client.ts" - "frontend/src/pages/CreatorDetail.tsx" - "frontend/src/App.css" GSD-Task: S03/T02
This commit is contained in:
parent
539274ce58
commit
47014f5a3f
6 changed files with 225 additions and 19 deletions
|
|
@ -29,7 +29,7 @@
|
||||||
- Estimate: 20m
|
- Estimate: 20m
|
||||||
- Files: backend/schemas.py, backend/routers/creators.py
|
- Files: backend/schemas.py, backend/routers/creators.py
|
||||||
- Verify: cd backend && python -c "from schemas import CreatorTechniqueItem; fields = list(CreatorTechniqueItem.model_fields.keys()); assert 'summary' in fields and 'topic_tags' in fields and 'key_moment_count' in fields; print('OK')"
|
- Verify: cd backend && python -c "from schemas import CreatorTechniqueItem; fields = list(CreatorTechniqueItem.model_fields.keys()); assert 'summary' in fields and 'topic_tags' in fields and 'key_moment_count' in fields; print('OK')"
|
||||||
- [ ] **T02: Add featured technique card and restyle grid to recent-card pattern** — Update the frontend to consume the enriched technique fields: render the first technique as a featured card with gradient border, and restyle remaining cards to match the recent-card pattern.
|
- [x] **T02: Featured technique card with gradient border and enriched recent-card grid rendered on creator detail page** — Update the frontend to consume the enriched technique fields: render the first technique as a featured card with gradient border, and restyle remaining cards to match the recent-card pattern.
|
||||||
|
|
||||||
## Steps
|
## Steps
|
||||||
|
|
||||||
|
|
|
||||||
16
.gsd/milestones/M017/slices/S03/tasks/T01-VERIFY.json
Normal file
16
.gsd/milestones/M017/slices/S03/tasks/T01-VERIFY.json
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"taskId": "T01",
|
||||||
|
"unitId": "M017/S03/T01",
|
||||||
|
"timestamp": 1775207254785,
|
||||||
|
"passed": true,
|
||||||
|
"discoverySource": "task-plan",
|
||||||
|
"checks": [
|
||||||
|
{
|
||||||
|
"command": "cd backend",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 4,
|
||||||
|
"verdict": "pass"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
82
.gsd/milestones/M017/slices/S03/tasks/T02-SUMMARY.md
Normal file
82
.gsd/milestones/M017/slices/S03/tasks/T02-SUMMARY.md
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
---
|
||||||
|
id: T02
|
||||||
|
parent: S03
|
||||||
|
milestone: M017
|
||||||
|
provides: []
|
||||||
|
requires: []
|
||||||
|
affects: []
|
||||||
|
key_files: ["frontend/src/api/public-client.ts", "frontend/src/pages/CreatorDetail.tsx", "frontend/src/App.css"]
|
||||||
|
key_decisions: ["Used null-check guard + IIFE for featured technique to satisfy strict TS noUncheckedIndexedAccess"]
|
||||||
|
patterns_established: []
|
||||||
|
drill_down_paths: []
|
||||||
|
observability_surfaces: []
|
||||||
|
duration: ""
|
||||||
|
verification_result: "npx tsc --noEmit exits 0. npm run build exits 0. grep confirms creator-featured and recent-card classes in CreatorDetail.tsx. Backend schema check confirms enriched fields present."
|
||||||
|
completed_at: 2026-04-03T09:10:03.442Z
|
||||||
|
blocker_discovered: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# T02: Featured technique card with gradient border and enriched recent-card grid rendered on creator detail page
|
||||||
|
|
||||||
|
> Featured technique card with gradient border and enriched recent-card grid rendered on creator detail page
|
||||||
|
|
||||||
|
## What Happened
|
||||||
|
---
|
||||||
|
id: T02
|
||||||
|
parent: S03
|
||||||
|
milestone: M017
|
||||||
|
key_files:
|
||||||
|
- frontend/src/api/public-client.ts
|
||||||
|
- frontend/src/pages/CreatorDetail.tsx
|
||||||
|
- frontend/src/App.css
|
||||||
|
key_decisions:
|
||||||
|
- Used null-check guard + IIFE for featured technique to satisfy strict TS noUncheckedIndexedAccess
|
||||||
|
duration: ""
|
||||||
|
verification_result: passed
|
||||||
|
completed_at: 2026-04-03T09:10:03.442Z
|
||||||
|
blocker_discovered: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# T02: Featured technique card with gradient border and enriched recent-card grid rendered on creator detail page
|
||||||
|
|
||||||
|
**Featured technique card with gradient border and enriched recent-card grid rendered on creator detail page**
|
||||||
|
|
||||||
|
## What Happened
|
||||||
|
|
||||||
|
Extended CreatorTechniqueItem TypeScript interface with summary, topic_tags, and key_moment_count. Restructured CreatorDetail technique list: first technique renders as a prominent featured card with gradient border-image, summary, category badge, TagList, and moment count. Remaining techniques use the recent-card class pattern with the same enriched fields. Added .creator-featured CSS adapted from .home-featured.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
npx tsc --noEmit exits 0. npm run build exits 0. grep confirms creator-featured and recent-card classes in CreatorDetail.tsx. Backend schema check confirms enriched fields present.
|
||||||
|
|
||||||
|
## Verification Evidence
|
||||||
|
|
||||||
|
| # | Command | Exit Code | Verdict | Duration |
|
||||||
|
|---|---------|-----------|---------|----------|
|
||||||
|
| 1 | `cd frontend && npx tsc --noEmit` | 0 | ✅ pass | 3100ms |
|
||||||
|
| 2 | `cd frontend && npm run build` | 0 | ✅ pass | 3100ms |
|
||||||
|
| 3 | `grep -q 'creator-featured' frontend/src/pages/CreatorDetail.tsx` | 0 | ✅ pass | 50ms |
|
||||||
|
| 4 | `grep -q 'recent-card' frontend/src/pages/CreatorDetail.tsx` | 0 | ✅ pass | 50ms |
|
||||||
|
| 5 | `cd backend && python -c "from schemas import CreatorTechniqueItem; ..."` | 0 | ✅ pass | 500ms |
|
||||||
|
|
||||||
|
|
||||||
|
## Deviations
|
||||||
|
|
||||||
|
Added null-check guard before IIFE for featured technique to satisfy Vite's stricter TS build config (noUncheckedIndexedAccess).
|
||||||
|
|
||||||
|
## Known Issues
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
|
||||||
|
- `frontend/src/api/public-client.ts`
|
||||||
|
- `frontend/src/pages/CreatorDetail.tsx`
|
||||||
|
- `frontend/src/App.css`
|
||||||
|
|
||||||
|
|
||||||
|
## Deviations
|
||||||
|
Added null-check guard before IIFE for featured technique to satisfy Vite's stricter TS build config (noUncheckedIndexedAccess).
|
||||||
|
|
||||||
|
## Known Issues
|
||||||
|
None.
|
||||||
|
|
@ -2806,6 +2806,60 @@ a.app-footer__repo:hover {
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Creator Featured Technique ─────────────────────────────────────── */
|
||||||
|
|
||||||
|
.creator-featured {
|
||||||
|
display: block;
|
||||||
|
max-width: 100%;
|
||||||
|
margin-bottom: 1.25rem;
|
||||||
|
padding: 1.25rem 1.5rem;
|
||||||
|
background: var(--color-bg-surface);
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-image: linear-gradient(135deg, #22d3ee, #a855f7) 1;
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.creator-featured__label {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.creator-featured__title {
|
||||||
|
display: block;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--color-text);
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.creator-featured:hover .creator-featured__title {
|
||||||
|
color: var(--color-accent-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.creator-featured__summary {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.creator-featured__meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.creator-featured__moments {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--color-text-tertiary);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
/* ══════════════════════════════════════════════════════════════════════════════
|
/* ══════════════════════════════════════════════════════════════════════════════
|
||||||
TOPICS BROWSE — Card Grid Layout
|
TOPICS BROWSE — Card Grid Layout
|
||||||
══════════════════════════════════════════════════════════════════════════════ */
|
══════════════════════════════════════════════════════════════════════════════ */
|
||||||
|
|
|
||||||
|
|
@ -178,6 +178,9 @@ export interface CreatorTechniqueItem {
|
||||||
title: string;
|
title: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
topic_category: string;
|
topic_category: string;
|
||||||
|
summary: string | null;
|
||||||
|
topic_tags: string[] | null;
|
||||||
|
key_moment_count: number;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import {
|
||||||
import CreatorAvatar from "../components/CreatorAvatar";
|
import CreatorAvatar from "../components/CreatorAvatar";
|
||||||
import { SocialIcon } from "../components/SocialIcons";
|
import { SocialIcon } from "../components/SocialIcons";
|
||||||
import SortDropdown from "../components/SortDropdown";
|
import SortDropdown from "../components/SortDropdown";
|
||||||
|
import TagList from "../components/TagList";
|
||||||
import { catSlug } from "../utils/catSlug";
|
import { catSlug } from "../utils/catSlug";
|
||||||
import { useDocumentTitle } from "../hooks/useDocumentTitle";
|
import { useDocumentTitle } from "../hooks/useDocumentTitle";
|
||||||
import { useSortPreference } from "../hooks/useSortPreference";
|
import { useSortPreference } from "../hooks/useSortPreference";
|
||||||
|
|
@ -184,26 +185,76 @@ export default function CreatorDetail() {
|
||||||
{techniques.length === 0 ? (
|
{techniques.length === 0 ? (
|
||||||
<div className="empty-state">No techniques yet.</div>
|
<div className="empty-state">No techniques yet.</div>
|
||||||
) : (
|
) : (
|
||||||
|
<>
|
||||||
|
{/* Featured technique — first in sorted order */}
|
||||||
|
{techniques[0] != null && (() => {
|
||||||
|
const featured = techniques[0];
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
to={`/techniques/${featured.slug}`}
|
||||||
|
className="creator-featured"
|
||||||
|
>
|
||||||
|
<span className="creator-featured__label section-heading section-heading--accent">
|
||||||
|
Featured Technique
|
||||||
|
</span>
|
||||||
|
<span className="creator-featured__title">
|
||||||
|
{featured.title}
|
||||||
|
</span>
|
||||||
|
{featured.summary && (
|
||||||
|
<p className="creator-featured__summary">
|
||||||
|
{featured.summary}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<span className="creator-featured__meta">
|
||||||
|
<span className={`badge badge--cat-${catSlug(featured.topic_category)}`}>
|
||||||
|
{featured.topic_category}
|
||||||
|
</span>
|
||||||
|
{featured.topic_tags && featured.topic_tags.length > 0 && (
|
||||||
|
<TagList tags={featured.topic_tags} max={4} pillClass="pill--tag" />
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span className="creator-featured__moments">
|
||||||
|
{featured.key_moment_count} moment{featured.key_moment_count !== 1 ? "s" : ""}
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
|
||||||
|
{/* Remaining techniques — recent-card grid */}
|
||||||
|
{techniques.length > 1 && (
|
||||||
<div className="creator-techniques__list">
|
<div className="creator-techniques__list">
|
||||||
{techniques.map((t, i) => (
|
{techniques.slice(1).map((t, i) => (
|
||||||
<Link
|
<Link
|
||||||
key={t.slug}
|
key={t.slug}
|
||||||
to={`/techniques/${t.slug}`}
|
to={`/techniques/${t.slug}`}
|
||||||
className="creator-technique-card card-stagger"
|
className="recent-card card-stagger"
|
||||||
style={{ '--stagger-index': i } as React.CSSProperties}
|
style={{ '--stagger-index': i } as React.CSSProperties}
|
||||||
>
|
>
|
||||||
<span className="creator-technique-card__title">
|
<span className="recent-card__title">
|
||||||
{t.title}
|
{t.title}
|
||||||
</span>
|
</span>
|
||||||
<span className="creator-technique-card__meta">
|
{t.summary && (
|
||||||
<span className="badge badge--category">
|
<span className="recent-card__summary">
|
||||||
|
{t.summary}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<span className="recent-card__meta">
|
||||||
|
<span className={`badge badge--cat-${catSlug(t.topic_category)}`}>
|
||||||
{t.topic_category}
|
{t.topic_category}
|
||||||
</span>
|
</span>
|
||||||
|
{t.topic_tags && t.topic_tags.length > 0 && (
|
||||||
|
<TagList tags={t.topic_tags} max={3} pillClass="pill--tag" />
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span className="recent-card__moments">
|
||||||
|
{t.key_moment_count} moment{t.key_moment_count !== 1 ? "s" : ""}
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue