feat: Added popular topics pill-link section to homepage that fetches f…
- "frontend/src/pages/Home.tsx" - "frontend/src/App.css" GSD-Task: S01/T02
This commit is contained in:
parent
07baa5aca1
commit
a41efec4e4
5 changed files with 176 additions and 1 deletions
|
|
@ -24,7 +24,7 @@ Constraints:
|
||||||
- Estimate: 30m
|
- Estimate: 30m
|
||||||
- Files: frontend/src/pages/Home.tsx, frontend/src/App.css
|
- Files: frontend/src/pages/Home.tsx, frontend/src/App.css
|
||||||
- Verify: cd frontend && npm run build 2>&1 | tail -5
|
- Verify: cd frontend && npm run build 2>&1 | tail -5
|
||||||
- [ ] **T02: Add popular topic quick-links fetched from topics API** — Add a popular topics section to the homepage that fetches topic data from the existing fetchTopics API, sorts sub-topics by technique_count, and renders the top 8-10 as clickable pill/chip links. Each pill navigates to the search page scoped to that topic.
|
- [x] **T02: Added popular topics pill-link section to homepage that fetches from topics API, sorts sub-topics by technique_count, and renders top 8 as clickable pills** — Add a popular topics section to the homepage that fetches topic data from the existing fetchTopics API, sorts sub-topics by technique_count, and renders the top 8-10 as clickable pill/chip links. Each pill navigates to the search page scoped to that topic.
|
||||||
|
|
||||||
Steps:
|
Steps:
|
||||||
1. Read current `frontend/src/pages/Home.tsx` (will include T01 changes) and `frontend/src/api/public-client.ts` (for TopicCategory/TopicSubTopic interfaces and fetchTopics function)
|
1. Read current `frontend/src/pages/Home.tsx` (will include T01 changes) and `frontend/src/api/public-client.ts` (for TopicCategory/TopicSubTopic interfaces and fetchTopics function)
|
||||||
|
|
|
||||||
16
.gsd/milestones/M009/slices/S01/tasks/T01-VERIFY.json
Normal file
16
.gsd/milestones/M009/slices/S01/tasks/T01-VERIFY.json
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"taskId": "T01",
|
||||||
|
"unitId": "M009/S01/T01",
|
||||||
|
"timestamp": 1774935330455,
|
||||||
|
"passed": true,
|
||||||
|
"discoverySource": "task-plan",
|
||||||
|
"checks": [
|
||||||
|
{
|
||||||
|
"command": "cd frontend",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 6,
|
||||||
|
"verdict": "pass"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
76
.gsd/milestones/M009/slices/S01/tasks/T02-SUMMARY.md
Normal file
76
.gsd/milestones/M009/slices/S01/tasks/T02-SUMMARY.md
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
---
|
||||||
|
id: T02
|
||||||
|
parent: S01
|
||||||
|
milestone: M009
|
||||||
|
provides: []
|
||||||
|
requires: []
|
||||||
|
affects: []
|
||||||
|
key_files: ["frontend/src/pages/Home.tsx", "frontend/src/App.css"]
|
||||||
|
key_decisions: ["Used uppercase secondary-text heading for Popular Topics to differentiate from primary content hierarchy"]
|
||||||
|
patterns_established: []
|
||||||
|
drill_down_paths: []
|
||||||
|
observability_surfaces: []
|
||||||
|
duration: ""
|
||||||
|
verification_result: "cd frontend && npm run build — zero errors, clean build (52KB CSS, 224KB JS gzipped in 748ms)"
|
||||||
|
completed_at: 2026-03-31T05:37:07.483Z
|
||||||
|
blocker_discovered: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# T02: Added popular topics pill-link section to homepage that fetches from topics API, sorts sub-topics by technique_count, and renders top 8 as clickable pills
|
||||||
|
|
||||||
|
> Added popular topics pill-link section to homepage that fetches from topics API, sorts sub-topics by technique_count, and renders top 8 as clickable pills
|
||||||
|
|
||||||
|
## What Happened
|
||||||
|
---
|
||||||
|
id: T02
|
||||||
|
parent: S01
|
||||||
|
milestone: M009
|
||||||
|
key_files:
|
||||||
|
- frontend/src/pages/Home.tsx
|
||||||
|
- frontend/src/App.css
|
||||||
|
key_decisions:
|
||||||
|
- Used uppercase secondary-text heading for Popular Topics to differentiate from primary content hierarchy
|
||||||
|
duration: ""
|
||||||
|
verification_result: passed
|
||||||
|
completed_at: 2026-03-31T05:37:07.484Z
|
||||||
|
blocker_discovered: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# T02: Added popular topics pill-link section to homepage that fetches from topics API, sorts sub-topics by technique_count, and renders top 8 as clickable pills
|
||||||
|
|
||||||
|
**Added popular topics pill-link section to homepage that fetches from topics API, sorts sub-topics by technique_count, and renders top 8 as clickable pills**
|
||||||
|
|
||||||
|
## What Happened
|
||||||
|
|
||||||
|
Imported fetchTopics into Home.tsx, added useEffect that flattens all sub_topics across categories, sorts by technique_count descending, takes top 8. Renders as pill links to /search?q={topic}&scope=topics. Section hidden when no data or API error. CSS uses existing pill token system with bordered hover state transitioning to cyan accent.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
cd frontend && npm run build — zero errors, clean build (52KB CSS, 224KB JS gzipped in 748ms)
|
||||||
|
|
||||||
|
## Verification Evidence
|
||||||
|
|
||||||
|
| # | Command | Exit Code | Verdict | Duration |
|
||||||
|
|---|---------|-----------|---------|----------|
|
||||||
|
| 1 | `cd frontend && npm run build` | 0 | ✅ pass | 748ms |
|
||||||
|
|
||||||
|
|
||||||
|
## Deviations
|
||||||
|
|
||||||
|
Removed unused TopicCategory type import that caused TS6133
|
||||||
|
|
||||||
|
## Known Issues
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
|
||||||
|
- `frontend/src/pages/Home.tsx`
|
||||||
|
- `frontend/src/App.css`
|
||||||
|
|
||||||
|
|
||||||
|
## Deviations
|
||||||
|
Removed unused TopicCategory type import that caused TS6133
|
||||||
|
|
||||||
|
## Known Issues
|
||||||
|
None.
|
||||||
|
|
@ -953,6 +953,50 @@ a.app-footer__repo:hover {
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Popular topics quick-links ───────────────────────────────────────────── */
|
||||||
|
|
||||||
|
.home-popular-topics {
|
||||||
|
margin-top: 2.5rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-popular-topics__title {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-popular-topics__list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
max-width: 36rem;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pill--topic-quick {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.375rem 0.875rem;
|
||||||
|
border-radius: 9999px;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
font-weight: 500;
|
||||||
|
background: var(--color-pill-bg);
|
||||||
|
color: var(--color-pill-text);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: border-color 0.15s, background 0.15s, color 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pill--topic-quick:hover {
|
||||||
|
border-color: var(--color-accent);
|
||||||
|
background: var(--color-accent-subtle);
|
||||||
|
color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Search form ──────────────────────────────────────────────────────────── */
|
/* ── Search form ──────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
.search-container {
|
.search-container {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import { Link, useNavigate } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
searchApi,
|
searchApi,
|
||||||
fetchTechniques,
|
fetchTechniques,
|
||||||
|
fetchTopics,
|
||||||
type SearchResultItem,
|
type SearchResultItem,
|
||||||
type TechniqueListItem,
|
type TechniqueListItem,
|
||||||
} from "../api/public-client";
|
} from "../api/public-client";
|
||||||
|
|
@ -21,6 +22,7 @@ export default function Home() {
|
||||||
const [showDropdown, setShowDropdown] = useState(false);
|
const [showDropdown, setShowDropdown] = useState(false);
|
||||||
const [recent, setRecent] = useState<TechniqueListItem[]>([]);
|
const [recent, setRecent] = useState<TechniqueListItem[]>([]);
|
||||||
const [recentLoading, setRecentLoading] = useState(true);
|
const [recentLoading, setRecentLoading] = useState(true);
|
||||||
|
const [popularTopics, setPopularTopics] = useState<{name: string; count: number}[]>([]);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
|
|
@ -49,6 +51,26 @@ export default function Home() {
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Load popular topics
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false;
|
||||||
|
void (async () => {
|
||||||
|
try {
|
||||||
|
const categories = await fetchTopics();
|
||||||
|
const all = categories.flatMap((cat) =>
|
||||||
|
cat.sub_topics.map((st) => ({ name: st.name, count: st.technique_count }))
|
||||||
|
);
|
||||||
|
all.sort((a, b) => b.count - a.count);
|
||||||
|
if (!cancelled) setPopularTopics(all.slice(0, 8));
|
||||||
|
} catch {
|
||||||
|
// optional section — silently ignore
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Close dropdown on outside click
|
// Close dropdown on outside click
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleClick(e: MouseEvent) {
|
function handleClick(e: MouseEvent) {
|
||||||
|
|
@ -198,6 +220,23 @@ export default function Home() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Link to="/topics" className="btn home-cta">Start Exploring</Link>
|
<Link to="/topics" className="btn home-cta">Start Exploring</Link>
|
||||||
|
|
||||||
|
{popularTopics.length > 0 && (
|
||||||
|
<section className="home-popular-topics">
|
||||||
|
<h2 className="home-popular-topics__title">Popular Topics</h2>
|
||||||
|
<div className="home-popular-topics__list">
|
||||||
|
{popularTopics.map((topic) => (
|
||||||
|
<Link
|
||||||
|
key={topic.name}
|
||||||
|
to={`/search?q=${encodeURIComponent(topic.name)}&scope=topics`}
|
||||||
|
className="pill pill--topic-quick"
|
||||||
|
>
|
||||||
|
{topic.name}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Navigation cards */}
|
{/* Navigation cards */}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue