Initial commit

This commit is contained in:
2026-09-02 08:21:27 +02:00
commit 84eec0039e
609 changed files with 94880 additions and 0 deletions
+109
View File
@@ -0,0 +1,109 @@
import type { ReactNode } from 'react'
import { Link } from 'react-router-dom'
import { BrandMark } from '../../components/BrandMark'
import { Icon } from '../../components/Icon'
import type { IconName } from '../../components/Icon'
import { useInstanceFavicon } from '../../lib/usePublicConfig'
import { useLegal } from '../../lib/useLegal'
/**
* Rama strony publicznej.
*
* Domyslnie WASKA (`max-w-2xl`): wiekszosc stron publicznych to jeden formularz
* albo krotki komunikat, wygodniejszy na telefonie w waskiej kolumnie.
*
* `wide` przelacza na szerokosc panelu (`max-w-container`, 1200px) - dla stron,
* ktore maja miescic wiecej tresci obok siebie i wygladac jak reszta serwisu:
* strona zapisu na webinar oraz dokumenty prawne (regulamin, polityka).
* Szerokosc ustawia layout, a nie poszczegolna strona - zgodnie z regula, ze
* strona nie ustawia wlasnej szerokosci.
*/
export function PublicLayout({ children, wide = false }: { children: ReactNode; wide?: boolean }) {
useInstanceFavicon()
const legal = useLegal()
const company = legal.data?.company
// regulamin i polityka to gotowe dokumenty - sa zawsze dostepne,
// wiec odnosniki pokazujemy bezwarunkowo (o ile dane w ogole sie zaladowaly)
const ready = Boolean(legal.data)
const widthClass = wide ? 'max-w-container' : 'max-w-2xl'
return (
<div className="flex min-h-screen flex-col bg-slate-50">
<header className="border-b border-slate-100 bg-white">
<div className={`mx-auto flex w-full ${widthClass} items-center px-4 py-4`}>
<BrandMark />
</div>
</header>
<main className={`mx-auto w-full ${widthClass} flex-1 px-4 py-6 sm:py-10`}>{children}</main>
{/*
Stopka prawna: uczestnik webinaru MUSI mieć dostęp do regulaminu,
polityki prywatności i tożsamości administratora danych (RODO).
Puste dokumenty nie pokazują odnośnika - odnośnik donikąd jest gorszy
niż jego brak.
*/}
<footer className={`mx-auto w-full ${widthClass} space-y-2 px-4 pb-8 pt-4 pb-safe`}>
{ready && (
<p className="flex flex-wrap justify-center gap-x-4 gap-y-1 text-xs text-slate-500">
<Link to="/regulamin" className="hover:text-slate-700 hover:underline">
Regulamin
</Link>
<Link to="/polityka-prywatnosci" className="hover:text-slate-700 hover:underline">
Polityka prywatności
</Link>
</p>
)}
{company?.name && (
<p className="text-center text-xs text-slate-400">
{company.name}
{company.address ? `, ${company.address}` : ''}
</p>
)}
</footer>
</div>
)
}
/** Komunikat zajmujacy cala strone, na przyklad brak miejsc albo webinar odwolany. */
export function PublicNotice({
icon,
title,
text,
tone = 'neutral',
children,
}: {
icon: IconName
title: string
text: string
tone?: 'neutral' | 'success' | 'warning'
children?: ReactNode
}) {
const tones = {
neutral: 'border-slate-100 bg-white',
success: 'border-ok-500/20 bg-ok-50',
warning: 'border-warn-500/25 bg-warn-50',
}
const iconTones = {
neutral: 'text-slate-300',
success: 'text-ok-500',
warning: 'text-warn-500',
}
const textTones = {
neutral: 'text-slate-600',
success: 'text-ok-600',
warning: 'text-warn-600',
}
return (
<section className={`rounded-lg border p-6 text-center shadow-sm ${tones[tone]}`}>
<span className="mx-auto flex justify-center">
<Icon name={icon} size={36} className={iconTones[tone]} />
</span>
<h2 className="mt-3 text-lg font-semibold text-slate-900">{title}</h2>
<p className={`mx-auto mt-1.5 max-w-md text-sm leading-relaxed ${textTones[tone]}`}>{text}</p>
{children && <div className="mt-5">{children}</div>}
</section>
)
}