feat: Added featured technique spotlight section and converted recently…
- "frontend/src/pages/Home.tsx" - "frontend/src/App.css" GSD-Task: S03/T02
This commit is contained in:
parent
810f96a640
commit
348089d635
2 changed files with 136 additions and 7 deletions
|
|
@ -1173,8 +1173,78 @@ a.app-footer__repo:hover {
|
||||||
|
|
||||||
/* ── Recently Added section ───────────────────────────────────────────────── */
|
/* ── Recently Added section ───────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/* ── Featured technique spotlight ──────────────────────────────────────── */
|
||||||
|
|
||||||
|
.home-featured {
|
||||||
|
max-width: 42rem;
|
||||||
|
margin: 0 auto 1.5rem;
|
||||||
|
padding: 1.25rem 1.5rem;
|
||||||
|
background: var(--color-bg-surface);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-left: 3px solid var(--color-accent);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-featured__label {
|
||||||
|
font-size: 0.6875rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
color: var(--color-accent);
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-featured__title {
|
||||||
|
display: block;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--color-text);
|
||||||
|
text-decoration: none;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-featured__title:hover {
|
||||||
|
color: var(--color-accent-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-featured__summary {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-featured__meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-featured__moments {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--color-text-tertiary);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-featured__creator {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-featured__creator:hover {
|
||||||
|
color: var(--color-accent-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Recently added ───────────────────────────────────────────────────── */
|
||||||
|
|
||||||
.recent-section {
|
.recent-section {
|
||||||
max-width: 36rem;
|
max-width: 42rem;
|
||||||
margin: 0 auto 2rem;
|
margin: 0 auto 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1185,8 +1255,8 @@ a.app-footer__repo:hover {
|
||||||
}
|
}
|
||||||
|
|
||||||
.recent-list {
|
.recent-list {
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-direction: column;
|
grid-template-columns: repeat(2, 1fr);
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2209,6 +2279,14 @@ a.app-footer__repo:hover {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.recent-list {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-featured {
|
||||||
|
padding: 1rem 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.technique-header__title {
|
.technique-header__title {
|
||||||
font-size: 1.375rem;
|
font-size: 1.375rem;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ export default function Home() {
|
||||||
const [query, setQuery] = useState("");
|
const [query, setQuery] = useState("");
|
||||||
const [suggestions, setSuggestions] = useState<SearchResultItem[]>([]);
|
const [suggestions, setSuggestions] = useState<SearchResultItem[]>([]);
|
||||||
const [showDropdown, setShowDropdown] = useState(false);
|
const [showDropdown, setShowDropdown] = useState(false);
|
||||||
|
const [featured, setFeatured] = useState<TechniqueListItem | null>(null);
|
||||||
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 [popularTopics, setPopularTopics] = useState<{name: string; count: number}[]>([]);
|
||||||
|
|
@ -33,12 +34,28 @@ export default function Home() {
|
||||||
inputRef.current?.focus();
|
inputRef.current?.focus();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Load featured technique (random)
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false;
|
||||||
|
void (async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetchTechniques({ sort: "random", limit: 1 });
|
||||||
|
if (!cancelled && res.items.length > 0) setFeatured(res.items[0]);
|
||||||
|
} catch {
|
||||||
|
// silently ignore — optional section
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Load recently added techniques
|
// Load recently added techniques
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
void (async () => {
|
void (async () => {
|
||||||
try {
|
try {
|
||||||
const res = await fetchTechniques({ limit: 5 });
|
const res = await fetchTechniques({ sort: "recent", limit: 6 });
|
||||||
if (!cancelled) setRecent(res.items);
|
if (!cancelled) setRecent(res.items);
|
||||||
} catch {
|
} catch {
|
||||||
// silently ignore — not critical
|
// silently ignore — not critical
|
||||||
|
|
@ -255,6 +272,37 @@ export default function Home() {
|
||||||
</Link>
|
</Link>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{/* Featured Technique Spotlight */}
|
||||||
|
{featured && (
|
||||||
|
<section className="home-featured">
|
||||||
|
<h3 className="home-featured__label">Featured Technique</h3>
|
||||||
|
<Link to={`/techniques/${featured.slug}`} className="home-featured__title">
|
||||||
|
{featured.title}
|
||||||
|
</Link>
|
||||||
|
{featured.summary && (
|
||||||
|
<p className="home-featured__summary">{featured.summary}</p>
|
||||||
|
)}
|
||||||
|
<div className="home-featured__meta">
|
||||||
|
{featured.topic_category && (
|
||||||
|
<span className="badge badge--category">{featured.topic_category}</span>
|
||||||
|
)}
|
||||||
|
{featured.topic_tags && featured.topic_tags.length > 0 && featured.topic_tags.map(tag => (
|
||||||
|
<span key={tag} className="pill pill--tag">{tag}</span>
|
||||||
|
))}
|
||||||
|
{featured.key_moment_count > 0 && (
|
||||||
|
<span className="home-featured__moments">
|
||||||
|
{featured.key_moment_count} moment{featured.key_moment_count !== 1 ? "s" : ""}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{featured.creator_name && (
|
||||||
|
<Link to={`/creators/${featured.creator_slug}`} className="home-featured__creator">
|
||||||
|
by {featured.creator_name}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Recently Added */}
|
{/* Recently Added */}
|
||||||
<section className="recent-section">
|
<section className="recent-section">
|
||||||
<h3 className="recent-section__title">Recently Added</h3>
|
<h3 className="recent-section__title">Recently Added</h3>
|
||||||
|
|
@ -264,7 +312,10 @@ export default function Home() {
|
||||||
<div className="empty-state">No techniques yet.</div>
|
<div className="empty-state">No techniques yet.</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="recent-list">
|
<div className="recent-list">
|
||||||
{recent.map((t) => (
|
{recent
|
||||||
|
.filter((t) => t.id !== featured?.id)
|
||||||
|
.slice(0, 4)
|
||||||
|
.map((t) => (
|
||||||
<Link
|
<Link
|
||||||
key={t.id}
|
key={t.id}
|
||||||
to={`/techniques/${t.slug}`}
|
to={`/techniques/${t.slug}`}
|
||||||
|
|
@ -285,8 +336,8 @@ export default function Home() {
|
||||||
))}
|
))}
|
||||||
{t.summary && (
|
{t.summary && (
|
||||||
<span className="recent-card__summary">
|
<span className="recent-card__summary">
|
||||||
{t.summary.length > 100
|
{t.summary.length > 150
|
||||||
? `${t.summary.slice(0, 100)}…`
|
? `${t.summary.slice(0, 150)}…`
|
||||||
: t.summary}
|
: t.summary}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue