Skip to content

Commit 07eee7c

Browse files
committed
Вывести кейсы на главной после демонстрации и вынести карточку в общий компонент
1 parent 0d40bf2 commit 07eee7c

3 files changed

Lines changed: 131 additions & 97 deletions

File tree

‎src/app/(home)/page.tsx‎

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import Image from 'next/image';
22
import Link from 'next/link';
33
import { Rocket, Globe, Package } from 'lucide-react';
4+
import { showcasesSource } from '@/lib/source';
5+
import { ShowcaseCard } from '@/components/showcase-card';
46
import { DemoVideo } from '@/components/demo-video';
57
import { InstallCommand } from '@/components/install-command';
68

7-
export default function HomePage() {
9+
export default async function HomePage() {
10+
const showcases = showcasesSource
11+
.getPages()
12+
.filter((p) => p.slugs.length > 0 && !p.data.full);
813
return (
914
<div className="flex flex-col flex-1">
1015
{/* Hero */}
@@ -82,6 +87,29 @@ export default function HomePage() {
8287
</div>
8388
</section>
8489

90+
{/* Showcases */}
91+
<section className="px-4 pb-20">
92+
<div className="max-w-5xl mx-auto">
93+
<h2 className="text-3xl font-bold text-center mb-2">Кейсы</h2>
94+
<p className="text-fd-muted-foreground text-center mb-8">
95+
Проекты, использующие Razd. Клонируйте и запускайте одной командой.
96+
</p>
97+
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
98+
{showcases.map((page) => (
99+
<ShowcaseCard key={page.url} page={page} />
100+
))}
101+
</div>
102+
<div className="text-center mt-8">
103+
<Link
104+
href="/showcases"
105+
className="text-sm font-medium text-fd-primary hover:underline"
106+
>
107+
Все кейсы →
108+
</Link>
109+
</div>
110+
</div>
111+
</section>
112+
85113
{/* Quick start */}
86114
<section className="px-4 pb-20">
87115
<div className="max-w-3xl mx-auto">

‎src/app/showcases/page.tsx‎

Lines changed: 9 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,11 @@
11
import Link from 'next/link';
22
import { showcasesSource } from '@/lib/source';
3-
import { ProviderBadge } from '@/components/provider';
4-
5-
const iconMap: Record<string, { emoji: string; color: string }> = {
6-
nodedotjs: { emoji: '🟢', color: '#339933' },
7-
py: { emoji: '🐍', color: '#3776AB' },
8-
go: { emoji: '🔷', color: '#00ADD8' },
9-
rust: { emoji: '🦀', color: '#CE422B' },
10-
deno: { emoji: '🦕', color: '#000000' },
11-
docker: { emoji: '🐳', color: '#2496ED' },
12-
ruby: { emoji: '💎', color: '#CC342D' },
13-
lua: { emoji: '🌙', color: '#2C2D72' },
14-
luau: { emoji: '🌙', color: '#00A2FF' },
15-
zig: { emoji: '⚡', color: '#F7A41D' },
16-
laravel: { emoji: '🎯', color: '#FF2D20' },
17-
};
18-
19-
function ShowcaseIcon({ name }: { name: string }) {
20-
const mapped = iconMap[name];
21-
if (mapped) {
22-
return (
23-
<span
24-
className="inline-flex items-center justify-center w-10 h-10 rounded-lg text-xl"
25-
style={{ backgroundColor: mapped.color + '18' }}
26-
>
27-
{mapped.emoji}
28-
</span>
29-
);
30-
}
31-
return (
32-
<span className="inline-flex items-center justify-center w-10 h-10 rounded-lg text-xl bg-fd-secondary">
33-
📦
34-
</span>
35-
);
36-
}
3+
import { ShowcaseCard } from '@/components/showcase-card';
374

385
export default async function ShowcasesPage() {
396
const pages = showcasesSource
407
.getPages()
41-
.filter((p) => p.slugs.length > 0 && !(p.data as any).full);
8+
.filter((p) => p.slugs.length > 0 && !p.data.full);
429

4310
return (
4411
<div className="container mx-auto px-4 py-12 max-w-fd-container">
@@ -47,71 +14,17 @@ export default async function ShowcasesPage() {
4714
Проекты, использующие Razd. Клонируйте и запускайте одной командой.
4815
</p>
4916
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
50-
{pages.map((page) => {
51-
const data = page.data as any;
52-
const tags: string[] = data.tags || [];
53-
const source = data.source;
54-
const icon = data.icon || '';
55-
const provider = data.provider as 'mise' | 'devbox' | undefined;
56-
57-
return (
58-
<div
59-
key={page.url}
60-
className="group rounded-xl border border-fd-border bg-fd-card p-6 transition-all duration-300 hover:border-fd-primary/40 hover:shadow-lg flex flex-col"
61-
>
62-
<Link href={page.url} className="no-underline">
63-
<div className="flex items-center gap-3 mb-4">
64-
<ShowcaseIcon name={icon} />
65-
<h2 className="text-lg font-semibold group-hover:text-fd-primary transition-colors">
66-
{data.title}
67-
</h2>
68-
</div>
69-
<p className="text-fd-muted-foreground text-sm mb-4 line-clamp-3">
70-
{data.description}
71-
</p>
72-
{tags.length > 0 && (
73-
<div className="flex flex-wrap gap-2 mb-4">
74-
{provider && <ProviderBadge provider={provider} />}
75-
{tags.map((tag) => (
76-
<span
77-
key={tag}
78-
className="text-xs font-medium px-2.5 py-1 rounded-md bg-fd-secondary text-fd-muted-foreground"
79-
>
80-
{tag}
81-
</span>
82-
))}
83-
</div>
84-
)}
85-
</Link>
86-
<div className="flex items-center gap-3 mt-auto pt-4 border-t border-fd-border">
87-
<Link
88-
href={page.url}
89-
className="text-sm font-medium text-fd-primary hover:underline"
90-
>
91-
Подробнее →
92-
</Link>
93-
{source && (
94-
<a
95-
href={source}
96-
target="_blank"
97-
rel="noopener noreferrer"
98-
className="text-sm font-medium text-fd-muted-foreground hover:underline"
99-
>
100-
Репозиторий →
101-
</a>
102-
)}
103-
</div>
104-
</div>
105-
);
106-
})}
17+
{pages.map((page) => (
18+
<ShowcaseCard key={page.url} page={page} />
19+
))}
10720
</div>
10821
<p className="text-center text-fd-muted-foreground text-sm mt-12">
10922
Смотрите{' '}
110-
<Link href="/docs/stack" className="text-fd-foreground underline">
111-
документацию по стекам технологий
23+
<Link href="/docs" className="text-fd-primary hover:underline">
24+
документацию
11225
</Link>{' '}
113-
для подробных руководств.
26+
для подробностей.
11427
</p>
11528
</div>
11629
);
117-
}
30+
}

‎src/components/showcase-card.tsx‎

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import Link from 'next/link';
2+
import { ProviderBadge } from '@/components/provider';
3+
import type { showcasesSource } from '@/lib/source';
4+
5+
type ShowcasePage = (typeof showcasesSource)['$inferPage'];
6+
7+
const iconMap: Record<string, { emoji: string; color: string }> = {
8+
nodedotjs: { emoji: '🟢', color: '#339933' },
9+
py: { emoji: '🐍', color: '#3776AB' },
10+
go: { emoji: '🔷', color: '#00ADD8' },
11+
rust: { emoji: '🦀', color: '#CE422B' },
12+
deno: { emoji: '🦕', color: '#000000' },
13+
docker: { emoji: '🐳', color: '#2496ED' },
14+
ruby: { emoji: '💎', color: '#CC342D' },
15+
lua: { emoji: '🌙', color: '#2C2D72' },
16+
luau: { emoji: '🌙', color: '#00A2FF' },
17+
zig: { emoji: '⚡', color: '#F7A41D' },
18+
laravel: { emoji: '🎯', color: '#FF2D20' },
19+
};
20+
21+
export function ShowcaseIcon({ name }: { name: string }) {
22+
const mapped = iconMap[name];
23+
if (mapped) {
24+
return (
25+
<span
26+
className="inline-flex items-center justify-center w-10 h-10 rounded-lg text-xl shrink-0"
27+
style={{ backgroundColor: mapped.color + '18' }}
28+
>
29+
{mapped.emoji}
30+
</span>
31+
);
32+
}
33+
return (
34+
<span className="inline-flex items-center justify-center w-10 h-10 rounded-lg text-xl bg-fd-secondary shrink-0">
35+
📦
36+
</span>
37+
);
38+
}
39+
40+
export function ShowcaseCard({ page }: { page: ShowcasePage }) {
41+
const data = page.data;
42+
const tags: string[] = data.tags ?? [];
43+
const source = data.source;
44+
const icon = data.icon ?? '';
45+
const provider = data.provider;
46+
47+
return (
48+
<div className="group rounded-xl border border-fd-border bg-fd-card p-6 transition-all duration-300 hover:border-fd-primary/40 hover:shadow-lg flex flex-col">
49+
<Link href={page.url} className="no-underline">
50+
<div className="flex items-center gap-3 mb-4">
51+
<ShowcaseIcon name={icon} />
52+
<h2 className="text-lg font-semibold group-hover:text-fd-primary transition-colors">
53+
{data.title}
54+
</h2>
55+
</div>
56+
<p className="text-fd-muted-foreground text-sm mb-4 line-clamp-3">
57+
{data.description}
58+
</p>
59+
{tags.length > 0 && (
60+
<div className="flex flex-wrap gap-2 mb-4">
61+
{provider && <ProviderBadge provider={provider} />}
62+
{tags.map((tag) => (
63+
<span
64+
key={tag}
65+
className="text-xs font-medium px-2.5 py-1 rounded-md bg-fd-secondary text-fd-muted-foreground"
66+
>
67+
{tag}
68+
</span>
69+
))}
70+
</div>
71+
)}
72+
</Link>
73+
<div className="flex items-center gap-3 mt-auto pt-4 border-t border-fd-border">
74+
<Link
75+
href={page.url}
76+
className="text-sm font-medium text-fd-primary hover:underline"
77+
>
78+
Подробнее →
79+
</Link>
80+
{source && (
81+
<a
82+
href={source}
83+
target="_blank"
84+
rel="noopener noreferrer"
85+
className="text-sm font-medium text-fd-muted-foreground hover:underline"
86+
>
87+
Репозиторий →
88+
</a>
89+
)}
90+
</div>
91+
</div>
92+
);
93+
}

0 commit comments

Comments
 (0)