Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 36 additions & 3 deletions app/community/_content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
import Image from "next/image";
import { useEffect, useMemo, useState } from "react";
import { usePathname, useRouter } from "next/navigation";
import { Loader2, LogIn, RefreshCw, Send } from "lucide-react";
import { Loader2, LogIn, RefreshCw, Send, TrendingUp } from "lucide-react";
import { LocalizedLink } from "@/components/i18n/localized-link";
import { useCommunityFeed } from "@/lib/swr";
import { useUser } from "@/lib/auth/use-user";
import { useI18n } from "@/lib/i18n/context";
import { SORT_OPTIONS, type SortMethod } from "@/lib/community/leaderboard";
import { CommunityStatsCard } from "@/components/community/community-stats-card";

const PAGE_SIZE = 12;

Expand All @@ -34,6 +36,7 @@
const router = useRouter();
const pathname = usePathname();
const [offset, setOffset] = useState(initialOffset);
const [sortMethod, setSortMethod] = useState<SortMethod>("recent");
const normalizedSlug = useMemo(
() => initialSlug.trim().toLowerCase() || undefined,
[initialSlug]
Expand Down Expand Up @@ -94,8 +97,19 @@
};

return (
<section className="border-b border-border">
<div className="max-w-7xl mx-auto px-4 sm:px-6 md:px-12 py-10 md:py-14">
<section>
{/* Community Stats */}
<CommunityStatsCard
stats={{
totalSubmissions: data?.total || 0,
totalCollaborators: data?.collaborators || 0,
recentSubmissions: data?.recentCount || 0,
topStyle: data?.featured || null,
}}
/>

<div className="border-b border-border">

Check failure on line 111 in app/community/_content.tsx

View workflow job for this annotation

GitHub Actions / Typecheck

JSX element 'div' has no corresponding closing tag.
<div className="max-w-7xl mx-auto px-4 sm:px-6 md:px-12 py-10 md:py-14">
<div className="flex flex-col gap-5 md:flex-row md:items-end md:justify-between mb-10">
<div>
<p className="text-xs tracking-[0.16em] uppercase text-muted mb-3">
Expand Down Expand Up @@ -147,6 +161,25 @@
</div>
)}

{/* Sort Controls */}
<div className="mb-6 flex flex-wrap gap-2">
{SORT_OPTIONS.map((option) => (
<button
key={option.value}
onClick={() => setSortMethod(option.value)}
className={`flex items-center gap-2 px-3 py-1.5 rounded-md text-sm transition-colors ${
sortMethod === option.value
? "bg-foreground text-background"
: "border border-border hover:border-foreground"
}`}
title={option.description}
>
{option.icon}
{option.label}
</button>
))}
</div>

{error && (
<div className="mb-6 rounded-md border border-red-300 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-800 dark:bg-red-950/40 dark:text-red-300 flex items-center justify-between">
<span>{error.message || t("community.loadError")}</span>
Expand Down
193 changes: 193 additions & 0 deletions app/guides/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import { Metadata } from "next";
import { notFound } from "next/navigation";
import { Header } from "@/components/layout/header";
import { Footer } from "@/components/layout/footer";
import { styleGuides, generateStyleGuideMetadata } from "@/lib/seo/style-guides";
import { LocalizedLink } from "@/components/i18n/localized-link";

interface StyleGuidePageProps {
params: Promise<{ slug: string }>;
}

export async function generateMetadata({
params,
}: StyleGuidePageProps): Promise<Metadata> {
const { slug } = await params;
const guide = styleGuides[slug as keyof typeof styleGuides];

if (!guide) {
return {
title: "Guide Not Found",
description: "The design guide you are looking for does not exist.",
};
}

return generateStyleGuideMetadata(guide);
}

export default async function StyleGuidePage({ params }: StyleGuidePageProps) {
const { slug } = await params;
const guide = styleGuides[slug as keyof typeof styleGuides];

if (!guide) {
notFound();
}

return (
<div className="min-h-screen flex flex-col">
<Header />
<main className="flex-1">
{/* Hero */}
<section className="border-b border-border">
<div className="max-w-4xl mx-auto px-4 sm:px-6 md:px-12 py-12 md:py-20">
<div className="mb-4">
<LocalizedLink
href="/guides"
className="inline-flex items-center gap-1 text-sm text-muted hover:text-foreground transition-colors"
>
← Back to Guides
</LocalizedLink>
</div>

<h1 className="text-4xl md:text-5xl font-bold mb-4 text-balance">
{guide.nameEn}
</h1>

{guide.name !== guide.nameEn && (
<p className="text-lg text-muted mb-6">{guide.name}</p>
)}

<p className="text-lg text-muted leading-relaxed max-w-2xl mb-8">
{guide.descriptionEn}
</p>

{guide.influencedBy && guide.influencedBy.length > 0 && (
<div className="flex flex-wrap gap-2 mb-6">
<span className="text-sm text-muted">Influenced by:</span>
{guide.influencedBy.map((style) => (
<span
key={style}
className="px-3 py-1 bg-muted/20 text-sm rounded-full"
>
{style}
</span>
))}
</div>
)}
</div>
</section>

{/* Philosophy */}
<section className="border-b border-border">
<div className="max-w-4xl mx-auto px-4 sm:px-6 md:px-12 py-12 md:py-16">
<h2 className="text-3xl font-bold mb-4">Design Philosophy</h2>
<p className="text-lg text-muted leading-relaxed mb-8">
{guide.philosophyEn}
</p>

<div className="prose prose-invert max-w-none">
{/* Additional philosophy content can be added here */}
</div>
</div>
</section>

{/* History */}
<section className="border-b border-border">
<div className="max-w-4xl mx-auto px-4 sm:px-6 md:px-12 py-12 md:py-16">
<h2 className="text-3xl font-bold mb-4">Design History</h2>
<p className="text-lg text-muted leading-relaxed">{guide.historyEn}</p>
</div>
</section>

{/* Use Cases */}
{guide.useCases.length > 0 && (
<section className="border-b border-border">
<div className="max-w-4xl mx-auto px-4 sm:px-6 md:px-12 py-12 md:py-16">
<h2 className="text-3xl font-bold mb-8">Use Cases</h2>

<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{guide.useCases.map((useCase, idx) => (
<div
key={idx}
className="border border-border rounded-lg p-6 hover:border-foreground transition-colors"
>
<h3 className="font-bold text-lg mb-2">{useCase.titleEn}</h3>
<p className="text-sm text-muted mb-3">
{useCase.descriptionEn}
</p>
<div className="flex items-center justify-between text-xs">
<span className="px-2 py-1 bg-muted/20 rounded">
{useCase.industry}
</span>
</div>
</div>
))}
</div>
</div>
</section>
)}

{/* References */}
{guide.references.length > 0 && (
<section className="border-b border-border">
<div className="max-w-4xl mx-auto px-4 sm:px-6 md:px-12 py-12 md:py-16">
<h2 className="text-3xl font-bold mb-6">References & Resources</h2>

<div className="space-y-3">
{guide.references.map((ref, idx) => (
<a
key={idx}
href={ref.url}
target="_blank"
rel="noopener noreferrer"
className="flex items-start gap-3 p-3 border border-border rounded-lg hover:border-foreground hover:bg-muted/5 transition-colors group"
>
<span className="text-xs px-2 py-1 bg-muted/20 rounded shrink-0 mt-0.5">
{ref.type}
</span>
<div className="flex-1 min-w-0">
<p className="font-medium group-hover:underline">
{ref.title}
</p>
<p className="text-xs text-muted truncate">{ref.url}</p>
</div>
</a>
))}
</div>
</div>
</section>
)}

{/* Related Styles */}
{guide.influenced && guide.influenced.length > 0 && (
<section className="border-b border-border">
<div className="max-w-4xl mx-auto px-4 sm:px-6 md:px-12 py-12 md:py-16">
<h2 className="text-3xl font-bold mb-6">Related Design Styles</h2>

<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{guide.influenced.map((style) => (
<LocalizedLink
key={style}
href={`/styles/${style.toLowerCase().replace(/\s+/g, "-")}`}
className="flex items-center justify-between p-4 border border-border rounded-lg hover:border-foreground hover:bg-muted/5 transition-colors"
>
<span className="font-medium">{style}</span>
<span className="text-muted">→</span>
</LocalizedLink>
))}
</div>
</div>
</section>
)}
</main>
<Footer />
</div>
);
}

/**
* Generate static params for all available style guides
*/
export function generateStaticParams() {
return Object.keys(styleGuides).map((slug) => ({ slug }));
}
132 changes: 132 additions & 0 deletions app/guides/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { Metadata } from "next";
import { Header } from "@/components/layout/header";
import { Footer } from "@/components/layout/footer";
import { LocalizedLink } from "@/components/i18n/localized-link";
import { styleGuides } from "@/lib/seo/style-guides";
import { BookOpen, ArrowRight } from "lucide-react";

export const metadata: Metadata = {
title: "Design Style Guides - StyleKit",
description:
"Learn the history, philosophy, and best practices of popular design styles. Comprehensive guides to help you choose the right design direction for your project.",
keywords: [
"design guide",
"design history",
"design philosophy",
"design styles",
"UI design",
],
};

export default function GuidesPage() {
const guides = Object.values(styleGuides);

return (
<div className="min-h-screen flex flex-col">
<Header />
<main className="flex-1">
{/* Hero */}
<section className="border-b border-border">
<div className="max-w-7xl mx-auto px-4 sm:px-6 md:px-12 py-12 md:py-20">
<div className="max-w-3xl">
<div className="flex items-center gap-2 mb-4">
<BookOpen className="w-5 h-5 text-muted" />
<span className="text-xs tracking-widest uppercase text-muted">
Design Education
</span>
</div>

<h1 className="text-4xl md:text-5xl font-bold mb-4 text-balance">
Design Style Guides
</h1>

<p className="text-lg text-muted leading-relaxed mb-6">
Deep dive into the history, philosophy, and practical applications of popular web design styles. Learn what influenced each style, when to use it, and real-world examples from leading companies.
</p>

<p className="text-base text-muted/70">
These comprehensive guides are designed to help you understand design trends, make informed choices for your projects, and improve your design literacy.
</p>
</div>
</div>
</section>

{/* Guides Grid */}
<section className="border-b border-border">
<div className="max-w-7xl mx-auto px-4 sm:px-6 md:px-12 py-12 md:py-16">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{guides.map((guide) => (
<LocalizedLink
key={guide.slug}
href={`/guides/${guide.slug}`}
className="group border border-border rounded-lg p-6 hover:border-foreground hover:shadow-sm hover:bg-muted/2 transition-all"
>
<div className="flex items-start justify-between mb-3">
<div>
<h3 className="text-xl font-bold mb-1 group-hover:underline">
{guide.nameEn}
</h3>
{guide.name !== guide.nameEn && (
<p className="text-sm text-muted">{guide.name}</p>
)}
</div>
<ArrowRight className="w-5 h-5 text-muted group-hover:translate-x-1 transition-transform" />
</div>

<p className="text-sm text-muted leading-relaxed mb-4 line-clamp-2">
{guide.descriptionEn}
</p>

{guide.influencedBy && guide.influencedBy.length > 0 && (
<div className="flex flex-wrap gap-1 mb-3">
{guide.influencedBy.map((style) => (
<span
key={style}
className="text-xs px-2 py-0.5 bg-muted/20 text-muted rounded"
>
{style}
</span>
))}
</div>
)}

<div className="flex items-center gap-2 text-xs text-muted">
<span>
{guide.useCases.length} use case
{guide.useCases.length !== 1 ? "s" : ""}
</span>
<span>•</span>
<span>
{guide.references.length} reference
{guide.references.length !== 1 ? "s" : ""}
</span>
</div>
</LocalizedLink>
))}
</div>
</div>
</section>

{/* CTA */}
<section className="border-b border-border">
<div className="max-w-7xl mx-auto px-4 sm:px-6 md:px-12 py-12 md:py-16">
<div className="text-center max-w-2xl mx-auto">
<h2 className="text-3xl font-bold mb-4">Ready to apply these styles?</h2>
<p className="text-muted mb-6">
Browse our full design style collection and start building beautiful interfaces today.
</p>
<LocalizedLink
href="/styles"
className="inline-flex items-center gap-2 px-6 py-3 bg-foreground text-background hover:bg-foreground/90 transition-colors rounded-lg font-medium"
>
Browse All Styles
<ArrowRight className="w-4 h-4" />
</LocalizedLink>
</div>
</div>
</section>
</main>
<Footer />
</div>
);
}
Loading
Loading