regulamin i polityka na stronie głównej
Deploy PROD / deploy (push) Successful in 25s

This commit is contained in:
2026-09-02 10:56:41 +02:00
parent 7fc35a1955
commit 7b4d367a06
3 changed files with 89 additions and 38 deletions
@@ -426,16 +426,19 @@ public enum SettingKey {
"Linia w stopce: nazwa spółki, adres, numery rejestrowe.", "Linia w stopce: nazwa spółki, adres, numery rejestrowe.",
Requirement.OPTIONAL_EMPTY), Requirement.OPTIONAL_EMPTY),
/* /*
* Adresy dokumentow, a nie ich tresc. Regulamin i polityka prywatnosci * NADPISANIE adresu dokumentu, a nie jego tresc. Platforma ma wbudowany
* bywaja wspolne dla calej firmy i utrzymywane poza ta aplikacja, wiec * regulamin (UswiadUE) i polityke prywatnosci (RODO), skladane z danych
* wskazanie adresu jest tansze niz przepisywanie dokumentu do panelu. * firmy: strony `/regulamin` i `/polityka-prywatnosci` istnieja zawsze.
* Puste ukrywa odnosnik: lepiej go nie pokazac, niz prowadzic donikad. * Te dwa klucze sluza wylacznie instancji, ktora utrzymuje wspolny dla
* calej firmy dokument poza ta aplikacja i chce na niego kierowac.
* Puste NIE ukrywa juz odnosnika: stopka prowadzi wtedy do strony
* wbudowanej, ktora zawsze ma tresc.
*/ */
ACADEMY_TERMS_URL(SettingType.STRING, "", ACADEMY_TERMS_URL(SettingType.STRING, "",
"Adres regulaminu. Puste ukrywa odnośnik w stopce.", "Zewnętrzny adres regulaminu. Puste = wbudowany regulamin platformy.",
Requirement.OPTIONAL_EMPTY), Requirement.OPTIONAL_EMPTY),
ACADEMY_PRIVACY_URL(SettingType.STRING, "", ACADEMY_PRIVACY_URL(SettingType.STRING, "",
"Adres polityki prywatności. Puste ukrywa odnośnik w stopce.", "Zewnętrzny adres polityki prywatności. Puste = wbudowana polityka platformy.",
Requirement.OPTIONAL_EMPTY), Requirement.OPTIONAL_EMPTY),
/* /*
* Prog uznania lekcji wideo za obejrzana. Wartosc domyslna jest nizsza niz * Prog uznania lekcji wideo za obejrzana. Wartosc domyslna jest nizsza niz
+55 -27
View File
@@ -206,11 +206,17 @@ function initials(firstName: string | null, lastName: string | null) {
* wybrano 1a, bo miesci odnosniki serwisowe i dane spolki, ktore i tak * wybrano 1a, bo miesci odnosniki serwisowe i dane spolki, ktore i tak
* musza gdzies byc, a jednolinijkowa wersja wypycha je do tresci strony. * musza gdzies byc, a jednolinijkowa wersja wypycha je do tresci strony.
* *
* KAZDY ODNOSNIK JEST OPCJONALNY. * REGULAMIN I POLITYKA ZAWSZE MAJA DOKAD PROWADZIC.
* --------------------------------------------------------------------------- * ---------------------------------------------------------------------------
* Pusty adres regulaminu UKRYWA odnosnik, zamiast prowadzic pod pusta * Platforma ma WBUDOWANE dokumenty prawne (`/regulamin`,
* sciezke: odnosnik donikad jest gorszy niz jego brak. Znika sam odnosnik, * `/polityka-prywatnosci`): regulamin wedlug UswiadUE i polityke wedlug RODO,
* nie cala stopka. * skladane z danych firmy z ustawien. Odnosnik prowadzi wiec domyslnie do
* naszej wlasnej strony i NIGDY nie jest martwy.
*
* Adres zewnetrzny (`termsUrl`/`privacyUrl` z ustawien) jest wylacznie
* NADPISANIEM dla instancji, ktora utrzymuje wspolny dla firmy dokument poza
* ta aplikacja. Pusty adres nie ukrywa juz odnosnika: kieruje go na strone
* wbudowana, ktora zawsze istnieje.
*/ */
export function AcademyFooter({ export function AcademyFooter({
text, text,
@@ -218,7 +224,9 @@ export function AcademyFooter({
privacyUrl, privacyUrl,
}: { }: {
text?: string text?: string
/** Zewnetrzny adres regulaminu. Puste = strona wbudowana `/regulamin`. */
termsUrl?: string termsUrl?: string
/** Zewnetrzny adres polityki. Puste = strona wbudowana `/polityka-prywatnosci`. */
privacyUrl?: string privacyUrl?: string
}) { }) {
const { brandName } = usePublicConfig() const { brandName } = usePublicConfig()
@@ -262,31 +270,16 @@ export function AcademyFooter({
<div> <div>
<p className="text-sm font-semibold text-white">Dokumenty</p> <p className="text-sm font-semibold text-white">Dokumenty</p>
<nav className="mt-3 flex flex-col gap-2 text-sm"> <nav className="mt-3 flex flex-col gap-2 text-sm">
{/* Pusty adres ukrywa sam odnosnik, nie cala kolumne. */} {/*
{termsUrl ? ( Odnosnik prowadzi domyslnie do strony wbudowanej, ktora zawsze
<a istnieje; adres zewnetrzny z ustawien tylko go nadpisuje.
href={termsUrl} */}
target="_blank" <LegalLink internalPath="/regulamin" externalUrl={termsUrl}>
rel="noopener noreferrer"
className="text-slate-400 transition-colors duration-fast hover:text-white"
>
Regulamin Regulamin
</a> </LegalLink>
) : ( <LegalLink internalPath="/polityka-prywatnosci" externalUrl={privacyUrl}>
<span className="text-slate-500">Regulamin</span>
)}
{privacyUrl ? (
<a
href={privacyUrl}
target="_blank"
rel="noopener noreferrer"
className="text-slate-400 transition-colors duration-fast hover:text-white"
>
Polityka prywatności Polityka prywatności
</a> </LegalLink>
) : (
<span className="text-slate-500">Polityka prywatności</span>
)}
<Link <Link
to="/certyfikat" to="/certyfikat"
className="text-slate-400 transition-colors duration-fast hover:text-white" className="text-slate-400 transition-colors duration-fast hover:text-white"
@@ -307,3 +300,38 @@ export function AcademyFooter({
</footer> </footer>
) )
} }
/**
* Odnosnik do dokumentu prawnego w stopce.
*
* Bez adresu zewnetrznego prowadzi do strony WBUDOWANEJ (react-router `Link`,
* bez przeladowania). Z adresem zewnetrznym otwiera go w nowej karcie
* (`<a target="_blank">` z `rel="noopener noreferrer"`, bo adres pochodzi
* z ustawien i wskazuje obca domene). W obu przypadkach odnosnik istnieje:
* nie ma juz wariantu wyszarzonego, bo dokument wbudowany zawsze jest.
*/
function LegalLink({
internalPath,
externalUrl,
children,
}: {
internalPath: string
externalUrl?: string
children: ReactNode
}) {
const className =
'text-slate-400 transition-colors duration-fast hover:text-white'
if (externalUrl && externalUrl.trim()) {
return (
<a href={externalUrl} target="_blank" rel="noopener noreferrer" className={className}>
{children}
</a>
)
}
return (
<Link to={internalPath} className={className}>
{children}
</Link>
)
}
+23 -3
View File
@@ -1,4 +1,6 @@
import { PublicLayout } from './PublicLayout' import { Link } from 'react-router-dom'
import { AcademyLayout } from '../../academy/AcademyLayout'
import { Icon } from '../../components/Icon'
import { useLegal } from '../../lib/useLegal' import { useLegal } from '../../lib/useLegal'
/** /**
@@ -9,6 +11,15 @@ import { useLegal } from '../../lib/useLegal'
* obecne, a w miejsce danych firmy podstawiane sa wartosci z konfiguracji. * obecne, a w miejsce danych firmy podstawiane sa wartosci z konfiguracji.
* Obowiazek informacyjny RODO jest juz czescia tresci polityki, wiec strona * Obowiazek informacyjny RODO jest juz czescia tresci polityki, wiec strona
* tylko ja wyswietla. * tylko ja wyswietla.
*
* UZYWA `AcademyLayout`, nie `PublicLayout`.
* ---------------------------------------------------------------------------
* Regulamin i polityka to strony serwisu, do ktorych uzytkownik trafia ze
* stopki strony glownej, wiec maja miec ten sam naglowek i te sama stopke co
* strona glowna: pasek nawigacji (Kursy/Webinary/Cennik), odnosnik do panelu
* albo konta zaleznie od zalogowania oraz logo linkujace do strony glownej.
* `PublicLayout` jest waski i pozbawiony nawigacji - pasuje do formularza
* zapisu otwieranego z linku w mailu, nie do samodzielnej strony serwisu.
*/ */
export function LegalPage({ kind }: { kind: 'terms' | 'privacy' }) { export function LegalPage({ kind }: { kind: 'terms' | 'privacy' }) {
const legal = useLegal() const legal = useLegal()
@@ -17,8 +28,17 @@ export function LegalPage({ kind }: { kind: 'terms' | 'privacy' }) {
const html = kind === 'terms' ? legal.data?.termsHtml : legal.data?.privacyHtml const html = kind === 'terms' ? legal.data?.termsHtml : legal.data?.privacyHtml
return ( return (
<PublicLayout wide> <AcademyLayout>
<article className="space-y-6"> <article className="space-y-6">
{/* Wyrazny powrot do strony glownej nad tytulem, obok logo w naglowku. */}
<Link
to="/"
className="inline-flex items-center gap-1.5 text-sm text-slate-500 transition-colors duration-fast hover:text-slate-900"
>
<Icon name="arrow-left" size={16} />
Strona główna
</Link>
<h1 className="text-2xl font-semibold text-slate-900">{title}</h1> <h1 className="text-2xl font-semibold text-slate-900">{title}</h1>
{legal.isPending ? ( {legal.isPending ? (
@@ -32,6 +52,6 @@ export function LegalPage({ kind }: { kind: 'terms' | 'privacy' }) {
/> />
)} )}
</article> </article>
</PublicLayout> </AcademyLayout>
) )
} }