Compare commits

...

5 commits

Author SHA1 Message Date
Dariusz Niemczyk 9fc91d9480
feat: additionally sanitize slugs
Some checks failed
/ deploy (push) Failing after 40s
2025-02-15 01:17:49 +01:00
Dariusz Niemczyk 4bc8882620
feat: support markdown and add "privacy policy"
Some checks failed
/ deploy (push) Failing after 1s
2025-02-15 01:11:16 +01:00
Dariusz Niemczyk 281f3a9a89
feat: add a catch-all 404 2025-02-15 01:02:59 +01:00
Dariusz Niemczyk 0072eac5d2
feat: make language-selector more robust 2025-02-15 00:35:32 +01:00
Dariusz Niemczyk 615e4466de
feat: split nav in preparation for mdx 2025-02-15 00:35:20 +01:00
14 changed files with 2031 additions and 130 deletions

View file

@ -1,7 +1,14 @@
import createMDX from "@next/mdx";
import type { NextConfig } from "next"; import type { NextConfig } from "next";
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
output: "standalone", 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

File diff suppressed because it is too large Load diff

View file

@ -14,10 +14,15 @@
"@lingui/core": "^5.1.2", "@lingui/core": "^5.1.2",
"@lingui/macro": "^5.1.2", "@lingui/macro": "^5.1.2",
"@lingui/react": "^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-checkbox": "^1.1.4",
"@radix-ui/react-dialog": "^1.1.5", "@radix-ui/react-dialog": "^1.1.5",
"@radix-ui/react-label": "^2.1.2", "@radix-ui/react-label": "^2.1.2",
"@radix-ui/react-slot": "^1.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", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"form-data": "^4.0.1", "form-data": "^4.0.1",

View file

@ -0,0 +1,5 @@
import { notFound } from "next/navigation"
export default function NotFoundDummy() {
notFound()
}

View 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>
)
}

View 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

View file

@ -4,11 +4,11 @@ import dynamic from 'next/dynamic';
import { Nav } from "@/components/nav";
import { jgs7 } from '@/fonts'; import { jgs7 } from '@/fonts';
import { Translations } from "@/i18n/translations"; import { Translations } from "@/i18n/translations";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { ReactElement, useEffect, useRef } from "react"; import { ReactElement, useEffect, useRef } from "react";
import { MainpageNav } from './nav';
import { NewsletterPopup } from './newsletter-form'; import { NewsletterPopup } from './newsletter-form';
import { useTheme } from "./providers"; import { useTheme } from "./providers";
import { Skeleton } from './ui/skeleton'; import { Skeleton } from './ui/skeleton';
@ -150,7 +150,7 @@ export default function LandingPage(
return ( return (
<div> <div>
<Nav t={t} /> <MainpageNav t={t} />
<main className="flex flex-col min-h-screen grid-gap-10 gap-10 pb-12"> <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 "> <section id="hero" className="h-screen relative overflow-hidden dark:bg-black light:bg-white ">

View 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>
);
}

View file

@ -1,13 +1,10 @@
"use client" "use client"
import { Button } from "@/components/ui/button"
import { useColorSections } from "@/hooks/color-sections" import { useColorSections } from "@/hooks/color-sections"
import { type translations } from "@/i18n/translations" import { type translations } from "@/i18n/translations"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import { MoonIcon, SunIcon } from "lucide-react"
import { useRef } from "react" import { useRef } from "react"
import { MobileNav } from "./mobile-nav" import { MobileNav } from "./mobile-nav"
import { useTheme } from "./providers" import { NavContainer } from "./nav-container"
import { LanguageSelector } from "./ui/language-selector"
const linksOrder: Array<keyof (typeof translations.pl)["nav"]> = [ const linksOrder: Array<keyof (typeof translations.pl)["nav"]> = [
"hero", "hero",
@ -20,27 +17,17 @@ const linksOrder: Array<keyof (typeof translations.pl)["nav"]> = [
"contact", "contact",
] ]
export function Nav({ export function MainpageNav({
t, t,
}: { }: {
t: typeof translations.pl t: typeof translations.pl
}) { }) {
const { theme, setTheme } = useTheme()
const parent = useRef<HTMLDivElement>(null); const parent = useRef<HTMLDivElement>(null);
useColorSections(parent); useColorSections(parent);
return ( return (
<nav className="fixed top-0 left-0 right-0 backdrop-blur-xs bg-background/40 border-b z-[10000]"> <NavContainer title={t.nav.title}>
<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}> <div className="hidden md:flex md:items-center md:gap-4 lg:gap-8" ref={parent}>
{linksOrder.map((value) => ( {linksOrder.map((value) => (
@ -55,26 +42,11 @@ export function Nav({
</a> </a>
))} ))}
</div> </div>
<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>
{/* Mobile Navigation */}
<div className="md:hidden ml-2"> <div className="md:hidden ml-2">
<MobileNav t={t} linksOrder={linksOrder} /> <MobileNav t={t} linksOrder={linksOrder} />
</div> </div>
</div> </>
</div> </NavContainer>
</div>
</nav>
) )
} }

View file

@ -2,17 +2,23 @@
import { Lang } from "@/i18n/locales"; import { Lang } from "@/i18n/locales";
import Link from "next/link"; import Link from "next/link";
import { useParams } from "next/navigation"; import { useParams, usePathname } from "next/navigation";
export const LanguageSelector = () => { export const LanguageSelector = () => {
const params = useParams<{ locale: Lang }>(); const params = useParams<{ locale: Lang }>();
const pathname = usePathname()
const replacements = {
'pl': 'en',
'en': 'pl',
}
const lang = params?.locale || 'pl'; const lang = params?.locale || 'pl';
const hash = globalThis?.window?.location?.hash || ''; const changedLang = pathname.replace(`/${lang}/`, `/${replacements[lang]}/`)
if (lang === 'pl') return (<> 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 (<> if (lang === 'en') return (<>
<Link suppressHydrationWarning className="pt-1" href={`/pl${hash}`}>🇵🇱</Link></>); <Link suppressHydrationWarning className="pt-1" href={changedLang}>🇵🇱</Link></>);
}; };

View file

@ -1,4 +1,5 @@
@import "tailwindcss"; @import "tailwindcss";
@plugin "@tailwindcss/typography";
@plugin 'tailwindcss-animate'; @plugin 'tailwindcss-animate';

7
src/mdx-components.ts Normal file
View 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
View 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
View 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/)