feat/static-pages #21
|
|
@ -1,7 +1,14 @@
|
|||
import createMDX from "@next/mdx";
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
output: "standalone",
|
||||
pageExtensions: ["js", "jsx", "md", "mdx", "ts", "tsx"],
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
const withMDX = createMDX({
|
||||
// Add markdown plugins here, as desired
|
||||
});
|
||||
|
||||
// Merge MDX config with Next.js config
|
||||
export default withMDX(nextConfig);
|
||||
|
|
|
|||
1924
package-lock.json
generated
1924
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -14,10 +14,15 @@
|
|||
"@lingui/core": "^5.1.2",
|
||||
"@lingui/macro": "^5.1.2",
|
||||
"@lingui/react": "^5.1.2",
|
||||
"@mdx-js/loader": "^3.1.0",
|
||||
"@mdx-js/react": "^3.1.0",
|
||||
"@next/mdx": "^15.1.7",
|
||||
"@radix-ui/react-checkbox": "^1.1.4",
|
||||
"@radix-ui/react-dialog": "^1.1.5",
|
||||
"@radix-ui/react-label": "^2.1.2",
|
||||
"@radix-ui/react-slot": "^1.1.2",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@types/mdx": "^2.0.13",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"form-data": "^4.0.1",
|
||||
|
|
|
|||
5
src/app/[locale]/[...not-found]/page.tsx
Normal file
5
src/app/[locale]/[...not-found]/page.tsx
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { notFound } from "next/navigation"
|
||||
|
||||
export default function NotFoundDummy() {
|
||||
notFound()
|
||||
}
|
||||
38
src/app/[locale]/pages/[slug]/layout.tsx
Normal file
38
src/app/[locale]/pages/[slug]/layout.tsx
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import { NavContainer } from "@/components/nav-container";
|
||||
import { getLocale, Lang } from "@/i18n/locales";
|
||||
import { translations } from "@/i18n/translations";
|
||||
|
||||
export default async function MdxLayout({ children, params }: { children: React.ReactNode, params: Promise<{ locale: Lang }> }) {
|
||||
const { locale } = await (params);
|
||||
const currentLang = getLocale(locale);
|
||||
|
||||
const t = translations[currentLang];
|
||||
return (
|
||||
<div>
|
||||
<NavContainer title={t.nav.title} >{null}</NavContainer>
|
||||
<section className="container mx-auto px-4 py-10">
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<article
|
||||
className="prose prose-invert max-w-none
|
||||
prose-h1:font-press-start prose-h1:text-4xl md:prose-h1:text-5xl prose-h1:mb-8 prose-h1:text-center prose-h1:text-foreground
|
||||
prose-h2:font-press-start prose-h2:text-2xl md:prose-h2:text-3xl prose-h2:text-foreground/80
|
||||
prose-h3:font-press-start prose-h3:text-xl md:prose-h3:text-2xl prose-h3:text-foreground/60
|
||||
prose-h4:font-press-start prose-h4:text-l md:prose-h4:text-xl prose-h4:text-foreground/40
|
||||
prose-p:text-muted-foreground prose-p:leading-relaxed
|
||||
prose-a:text-foreground prose-a:no-underline hover:prose-a:text-foreground/80 prose-a:transition-colors
|
||||
prose-strong:text-foreground prose-strong:font-bold
|
||||
prose-code:text-foreground prose-code:bg-muted/20 prose-code:px-1 prose-code:rounded
|
||||
prose-pre:bg-muted/20 prose-pre:border prose-pre:border-muted
|
||||
prose-img:rounded-lg prose-img:border prose-img:border-muted
|
||||
prose-blockquote:border-primary prose-blockquote:text-muted-foreground
|
||||
prose-ul:text-muted-foreground prose-ol:text-muted-foreground
|
||||
prose-li:marker:text-foreground"
|
||||
>
|
||||
{children}
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
34
src/app/[locale]/pages/[slug]/page.tsx
Normal file
34
src/app/[locale]/pages/[slug]/page.tsx
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import { getLocale, Lang } from "@/i18n/locales"
|
||||
import { notFound } from "next/navigation"
|
||||
|
||||
export default async function Page({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string, locale: Lang }>
|
||||
}) {
|
||||
const { slug, locale } = await params
|
||||
const currentLocale = getLocale(locale)
|
||||
|
||||
const isReallyProperSlug = /^[a-zA-Z0-9_-]+$/.test(slug)
|
||||
|
||||
if (!isReallyProperSlug) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
try {
|
||||
const path = `@/pages/${currentLocale}/${slug}.mdx`
|
||||
const pagemodule = await import(path)
|
||||
const Post = pagemodule.default
|
||||
|
||||
return <Post />
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
notFound()
|
||||
}
|
||||
}
|
||||
|
||||
export function generateStaticParams() {
|
||||
return [{ slug: 'privacy' }]
|
||||
}
|
||||
|
||||
export const dynamicParams = false
|
||||
|
|
@ -4,11 +4,11 @@ import dynamic from 'next/dynamic';
|
|||
|
||||
|
||||
|
||||
import { Nav } from "@/components/nav";
|
||||
import { jgs7 } from '@/fonts';
|
||||
import { Translations } from "@/i18n/translations";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ReactElement, useEffect, useRef } from "react";
|
||||
import { MainpageNav } from './nav';
|
||||
import { NewsletterPopup } from './newsletter-form';
|
||||
import { useTheme } from "./providers";
|
||||
import { Skeleton } from './ui/skeleton';
|
||||
|
|
@ -150,7 +150,7 @@ export default function LandingPage(
|
|||
|
||||
return (
|
||||
<div>
|
||||
<Nav t={t} />
|
||||
<MainpageNav t={t} />
|
||||
<main className="flex flex-col min-h-screen grid-gap-10 gap-10 pb-12">
|
||||
|
||||
<section id="hero" className="h-screen relative overflow-hidden dark:bg-black light:bg-white ">
|
||||
|
|
|
|||
40
src/components/nav-container.tsx
Normal file
40
src/components/nav-container.tsx
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
'use client';
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { MoonIcon, SunIcon } from "lucide-react";
|
||||
import { useTheme } from "./providers";
|
||||
import { LanguageSelector } from "./ui/language-selector";
|
||||
|
||||
export function NavContainer({ children, title, }: { children: React.ReactNode, title: string }) {
|
||||
const { theme, setTheme } = useTheme();
|
||||
|
||||
return (
|
||||
<nav className="fixed top-0 left-0 right-0 backdrop-blur-xs bg-background/40 border-b z-[10000]">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="flex items-center justify-between h-16">
|
||||
<div className="flex gap-4">
|
||||
<LanguageSelector />
|
||||
<a href="#" className="text-xl font-bold tracking-tighter hover:text-primary transition-colors">
|
||||
<h1>{title}</h1>
|
||||
</a>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
{children}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
|
||||
className="ml-4"
|
||||
>
|
||||
{theme === "dark" ? (
|
||||
<SunIcon className="h-5 w-5" />
|
||||
) : (
|
||||
<MoonIcon className="h-5 w-5" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,13 +1,10 @@
|
|||
"use client"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { useColorSections } from "@/hooks/color-sections"
|
||||
import { type translations } from "@/i18n/translations"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { MoonIcon, SunIcon } from "lucide-react"
|
||||
import { useRef } from "react"
|
||||
import { MobileNav } from "./mobile-nav"
|
||||
import { useTheme } from "./providers"
|
||||
import { LanguageSelector } from "./ui/language-selector"
|
||||
import { NavContainer } from "./nav-container"
|
||||
|
||||
const linksOrder: Array<keyof (typeof translations.pl)["nav"]> = [
|
||||
"hero",
|
||||
|
|
@ -20,61 +17,36 @@ const linksOrder: Array<keyof (typeof translations.pl)["nav"]> = [
|
|||
"contact",
|
||||
]
|
||||
|
||||
export function Nav({
|
||||
export function MainpageNav({
|
||||
t,
|
||||
}: {
|
||||
t: typeof translations.pl
|
||||
}) {
|
||||
const { theme, setTheme } = useTheme()
|
||||
const parent = useRef<HTMLDivElement>(null);
|
||||
useColorSections(parent);
|
||||
|
||||
return (
|
||||
<nav className="fixed top-0 left-0 right-0 backdrop-blur-xs bg-background/40 border-b z-[10000]">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="flex items-center justify-between h-16">
|
||||
<div className="flex gap-4">
|
||||
<LanguageSelector />
|
||||
<a href="#" className="text-xl font-bold tracking-tighter hover:text-primary transition-colors">
|
||||
<h1>{t.nav.title}</h1>
|
||||
</a>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
{/* Desktop Navigation */}
|
||||
<div className="hidden md:flex md:items-center md:gap-4 lg:gap-8" ref={parent}>
|
||||
<NavContainer title={t.nav.title}>
|
||||
<>
|
||||
<div className="hidden md:flex md:items-center md:gap-4 lg:gap-8" ref={parent}>
|
||||
|
||||
{linksOrder.map((value) => (
|
||||
<a
|
||||
key={value}
|
||||
href={`#${value}`}
|
||||
className="text-sm md:text-md hover:text-primary transition-colors relative group will-change-[color]"
|
||||
>
|
||||
{t.nav[value]}
|
||||
<span data-sub={value} className={cn("absolute inset-x-0 -bottom-1 h-0.5 bg-primary transform scale-x-0 group-hover:scale-x-100 transition-transform will-change-transform", {
|
||||
})} />
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
|
||||
className="ml-4"
|
||||
{linksOrder.map((value) => (
|
||||
<a
|
||||
key={value}
|
||||
href={`#${value}`}
|
||||
className="text-sm md:text-md hover:text-primary transition-colors relative group will-change-[color]"
|
||||
>
|
||||
{theme === "dark" ? <SunIcon className="h-5 w-5" /> : <MoonIcon className="h-5 w-5" />}
|
||||
</Button>
|
||||
|
||||
|
||||
|
||||
{/* Mobile Navigation */}
|
||||
<div className="md:hidden ml-2">
|
||||
<MobileNav t={t} linksOrder={linksOrder} />
|
||||
</div>
|
||||
</div>
|
||||
{t.nav[value]}
|
||||
<span data-sub={value} className={cn("absolute inset-x-0 -bottom-1 h-0.5 bg-primary transform scale-x-0 group-hover:scale-x-100 transition-transform will-change-transform", {
|
||||
})} />
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div className="md:hidden ml-2">
|
||||
<MobileNav t={t} linksOrder={linksOrder} />
|
||||
</div>
|
||||
</>
|
||||
</NavContainer>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,17 +2,23 @@
|
|||
|
||||
import { Lang } from "@/i18n/locales";
|
||||
import Link from "next/link";
|
||||
import { useParams } from "next/navigation";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
|
||||
|
||||
export const LanguageSelector = () => {
|
||||
const params = useParams<{ locale: Lang }>();
|
||||
const pathname = usePathname()
|
||||
|
||||
const replacements = {
|
||||
'pl': 'en',
|
||||
'en': 'pl',
|
||||
}
|
||||
|
||||
const lang = params?.locale || 'pl';
|
||||
const hash = globalThis?.window?.location?.hash || '';
|
||||
const changedLang = pathname.replace(`/${lang}/`, `/${replacements[lang]}/`)
|
||||
|
||||
if (lang === 'pl') return (<>
|
||||
<Link suppressHydrationWarning className="pt-1" href={`/en${hash}`}>🇬🇧</Link></>);
|
||||
<Link suppressHydrationWarning className="pt-1" href={changedLang}>🇬🇧</Link></>);
|
||||
if (lang === 'en') return (<>
|
||||
<Link suppressHydrationWarning className="pt-1" href={`/pl${hash}`}>🇵🇱</Link></>);
|
||||
<Link suppressHydrationWarning className="pt-1" href={changedLang}>🇵🇱</Link></>);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
@import "tailwindcss";
|
||||
@plugin "@tailwindcss/typography";
|
||||
|
||||
@plugin 'tailwindcss-animate';
|
||||
|
||||
|
|
|
|||
7
src/mdx-components.ts
Normal file
7
src/mdx-components.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import type { MDXComponents } from "mdx/types";
|
||||
|
||||
export function useMDXComponents(components: MDXComponents): MDXComponents {
|
||||
return {
|
||||
...components,
|
||||
};
|
||||
}
|
||||
6
src/pages/en/privacy.mdx
Normal file
6
src/pages/en/privacy.mdx
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# Privacy policy
|
||||
|
||||
You will see a privacy policy soon.
|
||||
It's just a placeholder for now.
|
||||
|
||||
Return to the [homepage](/en/)
|
||||
6
src/pages/pl/privacy.mdx
Normal file
6
src/pages/pl/privacy.mdx
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# Polityka prywatności
|
||||
|
||||
Tu w przyszłości pojawi się polityka prywatności na temat wydarzenia.
|
||||
W tym momencie po prostu trzymamy sobie stronę.
|
||||
|
||||
Wróć do [strony głównej](/pl/)
|
||||
Loading…
Reference in a new issue