Skip to content

Commit dbcb200

Browse files
KhyFeeulises-jeremias
authored andcommitted
fix(footer): sync catalog stats with templates.json (#11)
Drive footer + StatsBar counts from getTemplatesData() so hardcoded extensions (8) stop drifting from the live CPA catalog.
1 parent 19e0fa2 commit dbcb200

5 files changed

Lines changed: 68 additions & 13 deletions

File tree

src/app/layout.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Inter, Space_Grotesk } from 'next/font/google';
33
import type React from 'react';
44
import '@/app/globals.css';
55
import { LayoutShell } from '@/components/layout-shell';
6+
import { catalogStatsFrom, getTemplatesData } from '@/lib/data';
67
import { jsonLdScript, organizationJsonLd, websiteJsonLd } from '@/lib/seo';
78

89
const siteUrl = 'https://create-awesome-python-app.vercel.app';
@@ -91,7 +92,10 @@ const spaceGrotesk = Space_Grotesk({
9192
weight: ['400', '500', '600', '700'],
9293
});
9394

94-
export default function RootLayout({ children }: { children: React.ReactNode }) {
95+
export default async function RootLayout({ children }: { children: React.ReactNode }) {
96+
const catalog = await getTemplatesData();
97+
const stats = catalogStatsFrom(catalog);
98+
9599
return (
96100
<html lang="en" className="dark">
97101
<body className={`${inter.variable} ${spaceGrotesk.variable} font-sans`}>
@@ -105,7 +109,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
105109
}),
106110
}}
107111
/>
108-
<LayoutShell>{children}</LayoutShell>
112+
<LayoutShell stats={stats}>{children}</LayoutShell>
109113
</body>
110114
</html>
111115
);

src/app/page.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@ import { TemplateCategories } from '@/components/template-categories';
1717
import { Button } from '@/components/ui/button';
1818
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
1919
import { DISCORD_INVITE_URL } from '@/lib/community';
20-
import { getTemplatesData } from '@/lib/data';
20+
import { catalogStatsFrom, getTemplatesData } from '@/lib/data';
2121

2222
const PRIMARY_COMMAND = 'uvx create-awesome-python-app my-app';
2323

2424
export default async function Home() {
25-
const { templates, categories } = await getTemplatesData();
25+
const catalog = await getTemplatesData();
26+
const { templates, categories } = catalog;
27+
const stats = catalogStatsFrom(catalog);
2628

2729
const flagshipTemplate = templates.find((t) => t.slug === 'fastapi-starter');
2830
const otherTemplates = templates.filter((t) => t.slug !== 'fastapi-starter');
@@ -72,7 +74,11 @@ export default async function Home() {
7274
sideVisual={<AnimatedTerminal />}
7375
/>
7476

75-
<StatsBar />
77+
<StatsBar
78+
templates={stats.templates}
79+
extensions={stats.extensions}
80+
categories={stats.categories}
81+
/>
7682

7783
<SaasAiBanner />
7884

src/components/layout-shell.tsx

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,19 @@ import { PerformanceProvider } from '@/components/performance-provider';
88
import { SiteHeader } from '@/components/site-header';
99
import { ThemeProvider } from '@/components/theme-provider';
1010

11-
export function LayoutShell({ children }: { children: ReactNode }) {
11+
type CatalogStats = {
12+
templates: number;
13+
extensions: number;
14+
categories: number;
15+
};
16+
17+
export function LayoutShell({
18+
children,
19+
stats = { templates: 5, extensions: 12, categories: 9 },
20+
}: {
21+
children: ReactNode;
22+
stats?: CatalogStats;
23+
}) {
1224
const [open, setOpen] = useState(false);
1325
return (
1426
<PerformanceProvider>
@@ -26,19 +38,22 @@ export function LayoutShell({ children }: { children: ReactNode }) {
2638
{children}
2739
</main>
2840
<footer className="w-full border-t mt-16">
29-
{/* Stats row */}
41+
{/* Stats row — counts from templates.json via getTemplatesData */}
3042
<div className="border-b border-border/50">
3143
<div className="container flex flex-wrap items-center justify-center gap-x-8 gap-y-2 py-4 text-xs text-muted-foreground">
3244
<span>
33-
<strong className="text-foreground font-display font-semibold">5</strong> templates
45+
<strong className="text-foreground font-display font-semibold">{stats.templates}</strong>{' '}
46+
templates
3447
</span>
3548
<span className="text-border">·</span>
3649
<span>
37-
<strong className="text-foreground font-display font-semibold">8</strong> extensions
50+
<strong className="text-foreground font-display font-semibold">{stats.extensions}</strong>{' '}
51+
extensions
3852
</span>
3953
<span className="text-border">·</span>
4054
<span>
41-
<strong className="text-foreground font-display font-semibold">9</strong> categories
55+
<strong className="text-foreground font-display font-semibold">{stats.categories}</strong>{' '}
56+
categories
4257
</span>
4358
<span className="text-border">·</span>
4459
<span>MIT licensed</span>

src/components/stats-bar.tsx

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ interface Stat {
88
suffix?: string;
99
}
1010

11-
const STATS: Stat[] = [
11+
const DEFAULT_STATS: Stat[] = [
1212
{ value: 5, label: 'Templates' },
1313
{ value: 12, label: 'Extensions' },
1414
{ value: 9, label: 'Categories' },
@@ -47,11 +47,26 @@ function Counter({ target, duration = 1200 }: { target: number; duration?: numbe
4747
return <span ref={ref}>{count}</span>;
4848
}
4949

50-
export function StatsBar() {
50+
export function StatsBar({
51+
templates,
52+
extensions,
53+
categories,
54+
}: {
55+
templates?: number;
56+
extensions?: number;
57+
categories?: number;
58+
} = {}) {
59+
const stats: Stat[] = [
60+
{ value: templates ?? (DEFAULT_STATS[0].value as number), label: 'Templates' },
61+
{ value: extensions ?? (DEFAULT_STATS[1].value as number), label: 'Extensions' },
62+
{ value: categories ?? (DEFAULT_STATS[2].value as number), label: 'Categories' },
63+
{ value: 'MIT', label: 'Licensed' },
64+
];
65+
5166
return (
5267
<div className="w-full border-y border-border/50 bg-muted/20">
5368
<div className="container flex flex-wrap items-center justify-center gap-x-10 gap-y-4 py-6 md:gap-x-16">
54-
{STATS.map((stat) => (
69+
{stats.map((stat) => (
5570
<div key={stat.label} className="flex flex-col items-center gap-0.5">
5671
<span className="font-display text-2xl font-bold text-foreground md:text-3xl">
5772
{typeof stat.value === 'number' ? <Counter target={stat.value} /> : stat.value}

src/lib/data.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,21 @@ import { type TemplatesData, templatesDataSchema } from './schemas';
33

44
const TEMPLATES_URL = 'https://raw.githubusercontent.com/Create-Python-App/cpa-templates/main/templates.json';
55

6+
/** Catalog counts for footer / stats bar — single source of truth. */
7+
export type CatalogStats = {
8+
templates: number;
9+
extensions: number;
10+
categories: number;
11+
};
12+
13+
export function catalogStatsFrom(data: TemplatesData): CatalogStats {
14+
return {
15+
templates: data.templates.length,
16+
extensions: data.extensions.length,
17+
categories: data.categories.length,
18+
};
19+
}
20+
621
export async function getTemplatesData(): Promise<TemplatesData> {
722
try {
823
const response = await fetch(TEMPLATES_URL, { next: { revalidate: 3600 } }); // Revalidate every hour

0 commit comments

Comments
 (0)