1st
This commit is contained in:
41
app/[locale]/blog/[slug]/page.js
Normal file
41
app/[locale]/blog/[slug]/page.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getBlogPostBySlug, isSupportedLocale, locales } from "@/lib/site-data";
|
||||
|
||||
const blogSlugs = ["german-articles-hacks", "meldezettel-vienna", "osd-vs-goethe"];
|
||||
|
||||
export function generateStaticParams() {
|
||||
return locales.flatMap((locale) => blogSlugs.map((slug) => ({ locale, slug })));
|
||||
}
|
||||
|
||||
export default async function BlogDetailPage({ params }) {
|
||||
const { locale, slug } = await params;
|
||||
|
||||
if (!isSupportedLocale(locale)) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const post = getBlogPostBySlug(locale, slug);
|
||||
|
||||
if (!post) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-white text-gray-800">
|
||||
<article className="mx-auto max-w-4xl px-6 py-16">
|
||||
<Link href={`/${locale}/blog`} className="text-sm font-semibold text-brand-purple">
|
||||
← {locale === "de" ? "Zurück zum Blog" : "Back to blog"}
|
||||
</Link>
|
||||
<div className="mt-6 inline-flex rounded-full bg-brand-purple/10 px-4 py-2 text-sm font-semibold text-brand-purple">
|
||||
{post.tag}
|
||||
</div>
|
||||
<h1 className="mt-6 text-4xl font-bold text-brand-dark">{post.title}</h1>
|
||||
<div className="mt-4 text-sm text-gray-500">{post.date}</div>
|
||||
<div className="mt-10 text-lg leading-8 text-gray-700">
|
||||
<p>{post.body}</p>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
49
app/[locale]/blog/page.js
Normal file
49
app/[locale]/blog/page.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getLandingContent, isSupportedLocale, locales } from "@/lib/site-data";
|
||||
|
||||
export function generateStaticParams() {
|
||||
return locales.map((locale) => ({ locale }));
|
||||
}
|
||||
|
||||
export default async function BlogPage({ params }) {
|
||||
const { locale } = await params;
|
||||
|
||||
if (!isSupportedLocale(locale)) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const { blogPosts, navigation, shared } = getLandingContent(locale);
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-brand-light text-gray-800">
|
||||
<section className="mx-auto max-w-6xl px-6 py-16">
|
||||
<Link href={`/${locale}`} className="text-sm font-semibold text-brand-purple">
|
||||
{navigation.homeLabel}
|
||||
</Link>
|
||||
<h1 className="mt-4 text-4xl font-bold text-brand-dark">{navigation.blog}</h1>
|
||||
<p className="mt-3 max-w-2xl text-gray-600">{shared.blogIntro}</p>
|
||||
<div className="mt-12 grid gap-8 md:grid-cols-3">
|
||||
{blogPosts.map((post) => (
|
||||
<article key={post.slug} className="overflow-hidden rounded-2xl bg-white shadow-sm">
|
||||
<div className="h-48 overflow-hidden">
|
||||
<Image src={post.image} alt={post.title} width={800} height={600} className="h-full w-full object-cover transition duration-500 hover:scale-110" />
|
||||
</div>
|
||||
<div className="p-6">
|
||||
<span className={`${post.tagClass} rounded px-2 py-1 text-xs font-bold`}>{post.tag}</span>
|
||||
<h2 className="mt-4 text-xl font-bold">
|
||||
<Link href={`/${locale}/blog/${post.slug}`} className="transition hover:text-brand-purple">
|
||||
{post.title}
|
||||
</Link>
|
||||
</h2>
|
||||
<p className="mt-3 text-sm text-gray-600">{post.excerpt}</p>
|
||||
<div className="mt-4 text-sm text-gray-500">{post.date}</div>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
49
app/[locale]/contact/page.js
Normal file
49
app/[locale]/contact/page.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getLandingContent, isSupportedLocale, locales } from "@/lib/site-data";
|
||||
|
||||
export function generateStaticParams() {
|
||||
return locales.map((locale) => ({ locale }));
|
||||
}
|
||||
|
||||
export default async function ContactPage({ params }) {
|
||||
const { locale } = await params;
|
||||
|
||||
if (!isSupportedLocale(locale)) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const { contact, shared } = getLandingContent(locale);
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-brand-light text-gray-800">
|
||||
<section className="mx-auto max-w-5xl px-6 py-16">
|
||||
<Link href={`/${locale}`} className="text-sm font-semibold text-brand-purple">
|
||||
{locale === "de" ? "Startseite" : "Home"}
|
||||
</Link>
|
||||
<div className="mt-6 grid gap-10 md:grid-cols-2">
|
||||
<div>
|
||||
<h1 className="text-4xl font-bold text-brand-dark">{shared.contactTitle}</h1>
|
||||
<p className="mt-4 text-gray-600">{shared.contactIntro}</p>
|
||||
<div className="mt-8 space-y-4 text-gray-700">
|
||||
<p>{contact.location}</p>
|
||||
<p>{contact.phone}</p>
|
||||
<p>{contact.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
<form className="rounded-3xl bg-white p-8 shadow-sm ring-1 ring-gray-100">
|
||||
<div className="grid gap-4">
|
||||
<input className="rounded-xl border border-gray-200 px-4 py-3" placeholder={shared.nameLabel} />
|
||||
<input className="rounded-xl border border-gray-200 px-4 py-3" placeholder={shared.emailLabel} />
|
||||
<input className="rounded-xl border border-gray-200 px-4 py-3" placeholder={shared.phoneLabel} />
|
||||
<textarea className="min-h-36 rounded-xl border border-gray-200 px-4 py-3" placeholder={shared.messageLabel} />
|
||||
<button className="rounded-xl bg-brand-purple px-5 py-3 font-bold text-white transition hover:bg-purple-700">
|
||||
{shared.sendMessage}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
40
app/[locale]/courses/[slug]/page.js
Normal file
40
app/[locale]/courses/[slug]/page.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getCourseBySlug, isSupportedLocale, locales } from "@/lib/site-data";
|
||||
|
||||
const courseSlugs = ["beginner-a1", "intermediate-b1", "advanced-c1"];
|
||||
|
||||
export function generateStaticParams() {
|
||||
return locales.flatMap((locale) => courseSlugs.map((slug) => ({ locale, slug })));
|
||||
}
|
||||
|
||||
export default async function CourseDetailPage({ params }) {
|
||||
const { locale, slug } = await params;
|
||||
|
||||
if (!isSupportedLocale(locale)) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const course = getCourseBySlug(locale, slug);
|
||||
|
||||
if (!course) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-white text-gray-800">
|
||||
<div className="mx-auto max-w-4xl px-6 py-16">
|
||||
<Link href={`/${locale}/courses`} className="text-sm font-semibold text-brand-purple">
|
||||
← {locale === "de" ? "Zurück zu Kursen" : "Back to courses"}
|
||||
</Link>
|
||||
<div className="mt-8 rounded-3xl bg-brand-light p-10">
|
||||
<div className="mb-6 flex h-16 w-16 items-center justify-center rounded-2xl bg-brand-purple text-2xl font-bold text-white">{course.level}</div>
|
||||
<h1 className="text-4xl font-bold text-brand-dark">{course.title}</h1>
|
||||
<p className="mt-4 text-lg text-gray-600">{course.summary}</p>
|
||||
<p className="mt-8 leading-8 text-gray-700">{course.body}</p>
|
||||
<div className="mt-8 text-2xl font-black text-brand-dark">{course.price}</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
48
app/[locale]/courses/page.js
Normal file
48
app/[locale]/courses/page.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getLandingContent, isSupportedLocale, locales } from "@/lib/site-data";
|
||||
|
||||
export function generateStaticParams() {
|
||||
return locales.map((locale) => ({ locale }));
|
||||
}
|
||||
|
||||
export default async function CoursesPage({ params }) {
|
||||
const { locale } = await params;
|
||||
|
||||
if (!isSupportedLocale(locale)) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const { courses, navigation, shared } = getLandingContent(locale);
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-brand-light text-gray-800">
|
||||
<section className="border-b border-gray-200 bg-white">
|
||||
<div className="mx-auto max-w-6xl px-6 py-16">
|
||||
<Link href={`/${locale}`} className="text-sm font-semibold text-brand-purple">
|
||||
{navigation.homeLabel}
|
||||
</Link>
|
||||
<h1 className="mt-4 text-4xl font-bold text-brand-dark">{navigation.courses}</h1>
|
||||
<p className="mt-3 max-w-2xl text-gray-600">{shared.coursesIntro}</p>
|
||||
</div>
|
||||
</section>
|
||||
<section className="mx-auto grid max-w-6xl gap-8 px-6 py-16 md:grid-cols-3">
|
||||
{courses.map((course) => (
|
||||
<article key={course.slug} className="rounded-3xl bg-white p-8 shadow-sm ring-1 ring-gray-100">
|
||||
<div className="mb-6 flex h-16 w-16 items-center justify-center rounded-2xl bg-brand-purple/10 text-2xl font-bold text-brand-purple">
|
||||
{course.level}
|
||||
</div>
|
||||
<h2 className="text-2xl font-bold">{course.title}</h2>
|
||||
<p className="mt-3 text-gray-600">{course.summary}</p>
|
||||
<div className="mt-6 text-2xl font-black text-brand-dark">
|
||||
{course.price} <span className="text-sm font-normal text-gray-400">/ {shared.perLevel}</span>
|
||||
</div>
|
||||
<Link href={`/${locale}/courses/${course.slug}`} className="mt-6 inline-flex rounded-xl border-2 border-brand-dark px-5 py-3 font-bold transition hover:bg-brand-dark hover:text-white">
|
||||
{shared.viewDetails}
|
||||
</Link>
|
||||
</article>
|
||||
))}
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
33
app/[locale]/faq/page.js
Normal file
33
app/[locale]/faq/page.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { FaqAccordion } from "@/components/site/faq-accordion";
|
||||
import { getLandingContent, isSupportedLocale, locales } from "@/lib/site-data";
|
||||
|
||||
export function generateStaticParams() {
|
||||
return locales.map((locale) => ({ locale }));
|
||||
}
|
||||
|
||||
export default async function FaqPage({ params }) {
|
||||
const { locale } = await params;
|
||||
|
||||
if (!isSupportedLocale(locale)) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const { faqItems, shared } = getLandingContent(locale);
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-white text-gray-800">
|
||||
<section className="mx-auto max-w-4xl px-6 py-16">
|
||||
<Link href={`/${locale}`} className="text-sm font-semibold text-brand-purple">
|
||||
{locale === "de" ? "Startseite" : "Home"}
|
||||
</Link>
|
||||
<h1 className="mt-4 text-4xl font-bold text-brand-dark">{shared.faqTitle}</h1>
|
||||
<p className="mt-3 max-w-2xl text-gray-600">{shared.faqIntro}</p>
|
||||
<div className="mt-10">
|
||||
<FaqAccordion items={faqItems} />
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
32
app/[locale]/page.js
Normal file
32
app/[locale]/page.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import { notFound } from "next/navigation";
|
||||
import { LandingPage } from "@/components/site/landing-page";
|
||||
import { getLandingContent, isSupportedLocale, locales } from "@/lib/site-data";
|
||||
|
||||
export function generateStaticParams() {
|
||||
return locales.map((locale) => ({ locale }));
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }) {
|
||||
const { locale } = await params;
|
||||
|
||||
if (!isSupportedLocale(locale)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const content = getLandingContent(locale);
|
||||
|
||||
return {
|
||||
title: content.meta.title,
|
||||
description: content.meta.description,
|
||||
};
|
||||
}
|
||||
|
||||
export default async function LocaleHomePage({ params }) {
|
||||
const { locale } = await params;
|
||||
|
||||
if (!isSupportedLocale(locale)) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
return <LandingPage locale={locale} content={getLandingContent(locale)} />;
|
||||
}
|
||||
48
app/[locale]/placement-test/page.js
Normal file
48
app/[locale]/placement-test/page.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getLandingContent, isSupportedLocale, locales } from "@/lib/site-data";
|
||||
|
||||
export function generateStaticParams() {
|
||||
return locales.map((locale) => ({ locale }));
|
||||
}
|
||||
|
||||
export default async function PlacementTestPage({ params }) {
|
||||
const { locale } = await params;
|
||||
|
||||
if (!isSupportedLocale(locale)) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const { courses, shared } = getLandingContent(locale);
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-white text-gray-800">
|
||||
<section className="mx-auto max-w-5xl px-6 py-16">
|
||||
<Link href={`/${locale}`} className="text-sm font-semibold text-brand-purple">
|
||||
{locale === "de" ? "Startseite" : "Home"}
|
||||
</Link>
|
||||
<div className="mt-6 grid gap-10 md:grid-cols-2">
|
||||
<div>
|
||||
<h1 className="text-4xl font-bold text-brand-dark">{shared.placementTitle}</h1>
|
||||
<p className="mt-4 text-gray-600">{shared.placementIntro}</p>
|
||||
</div>
|
||||
<form className="rounded-3xl bg-brand-light p-8 shadow-sm ring-1 ring-gray-100">
|
||||
<div className="grid gap-4">
|
||||
<input className="rounded-xl border border-gray-200 px-4 py-3" placeholder={shared.nameLabel} />
|
||||
<input className="rounded-xl border border-gray-200 px-4 py-3" placeholder={shared.emailLabel} />
|
||||
<select className="rounded-xl border border-gray-200 px-4 py-3">
|
||||
{courses.map((course) => (
|
||||
<option key={course.slug}>{course.level}</option>
|
||||
))}
|
||||
</select>
|
||||
<textarea className="min-h-36 rounded-xl border border-gray-200 px-4 py-3" placeholder={shared.messageLabel} />
|
||||
<button className="rounded-xl bg-brand-purple px-5 py-3 font-bold text-white transition hover:bg-purple-700">
|
||||
{shared.requestPlacement}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
45
app/[locale]/teachers/page.js
Normal file
45
app/[locale]/teachers/page.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getLandingContent, isSupportedLocale, locales } from "@/lib/site-data";
|
||||
|
||||
export function generateStaticParams() {
|
||||
return locales.map((locale) => ({ locale }));
|
||||
}
|
||||
|
||||
export default async function TeachersPage({ params }) {
|
||||
const { locale } = await params;
|
||||
|
||||
if (!isSupportedLocale(locale)) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const { teachers, navigation, shared } = getLandingContent(locale);
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-brand-light text-gray-800">
|
||||
<section className="mx-auto max-w-6xl px-6 py-16">
|
||||
<Link href={`/${locale}`} className="text-sm font-semibold text-brand-purple">
|
||||
{navigation.homeLabel}
|
||||
</Link>
|
||||
<h1 className="mt-4 text-4xl font-bold text-brand-dark">{navigation.teachers}</h1>
|
||||
<p className="mt-3 max-w-2xl text-gray-600">{shared.teachersIntro}</p>
|
||||
<div className="mt-12 grid gap-8 sm:grid-cols-2 lg:grid-cols-4">
|
||||
{teachers.map((teacher) => (
|
||||
<article key={teacher.slug} className="overflow-hidden rounded-2xl bg-white shadow-sm">
|
||||
<Image src={teacher.image} alt={teacher.name} width={600} height={700} className="h-64 w-full object-cover" />
|
||||
<div className="p-6">
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<h2 className="text-xl font-bold">{teacher.name}</h2>
|
||||
<span className="text-xl" aria-hidden="true">{teacher.flag}</span>
|
||||
</div>
|
||||
<p className="mt-3 text-sm font-semibold text-brand-purple">{teacher.title}</p>
|
||||
<p className="mt-3 text-sm text-gray-500">{teacher.description}</p>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user