mirror of
https://github.com/Dvorinka/Bookra.git
synced 2026-06-04 12:33:00 +00:00
cleanup
This commit is contained in:
@@ -10,12 +10,57 @@ import type { AuthSession } from "../lib/types";
|
||||
|
||||
const neonAuthUrl = import.meta.env.VITE_NEON_AUTH_URL ?? "";
|
||||
const authClient = neonAuthUrl ? createAuthClient(neonAuthUrl) : null;
|
||||
const localAuthTokenKey = "bookra.localAuthToken";
|
||||
|
||||
type LocalUser = {
|
||||
id: string;
|
||||
email?: string;
|
||||
name?: string;
|
||||
};
|
||||
|
||||
function sessionFromLocalToken(token: string, user?: LocalUser): AuthSession {
|
||||
const payload = parseJwtPayload(token);
|
||||
const expiresAt = typeof payload.exp === "number" ? new Date(payload.exp * 1000) : undefined;
|
||||
return {
|
||||
session: {
|
||||
id: token,
|
||||
userId: String(payload.sub ?? user?.id ?? ""),
|
||||
token,
|
||||
expiresAt,
|
||||
},
|
||||
user: {
|
||||
id: String(payload.sub ?? user?.id ?? ""),
|
||||
email: String(payload.email ?? user?.email ?? ""),
|
||||
name: String(payload.name ?? user?.name ?? ""),
|
||||
image: null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function parseJwtPayload(token: string): Record<string, unknown> {
|
||||
try {
|
||||
const payload = token.split(".")[1];
|
||||
if (!payload) return {};
|
||||
const padded = payload.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(payload.length / 4) * 4, "=");
|
||||
return JSON.parse(atob(padded)) as Record<string, unknown>;
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
type AuthContextValue = {
|
||||
session: () => AuthSession | null;
|
||||
loading: () => boolean;
|
||||
usesNeonAuth: () => boolean;
|
||||
supportsMagicLink: () => boolean;
|
||||
supportsGoogleSignIn: () => boolean;
|
||||
getToken: () => Promise<string | null>;
|
||||
signInDemo: () => Promise<void>;
|
||||
signUpWithEmail: (name: string, email: string, password: string) => Promise<void>;
|
||||
signInWithEmail: (email: string, password: string) => Promise<void>;
|
||||
signInAsDemo: () => Promise<void>;
|
||||
sendMagicLink: (email: string) => Promise<void>;
|
||||
signInWithMagicLink: (token: string, refreshToken?: string) => Promise<void>;
|
||||
signInWithGoogle: () => Promise<void>;
|
||||
signOut: () => Promise<void>;
|
||||
};
|
||||
|
||||
@@ -25,9 +70,27 @@ export const AuthProvider: ParentComponent = (props) => {
|
||||
const [session, setSession] = createSignal<AuthSession | null>(null);
|
||||
const [loading, setLoading] = createSignal(true);
|
||||
|
||||
const clearLocalSession = () => {
|
||||
localStorage.removeItem(localAuthTokenKey);
|
||||
setSession(null);
|
||||
};
|
||||
|
||||
createEffect(() => {
|
||||
void (async () => {
|
||||
const demoToken = localStorage.getItem(localAuthTokenKey);
|
||||
if (demoToken?.startsWith("demo.")) {
|
||||
const demoUser: LocalUser = {
|
||||
id: "demo-user-id",
|
||||
email: "demo@bookra.io",
|
||||
name: "Demo User",
|
||||
};
|
||||
setSession(sessionFromLocalToken(demoToken, demoUser));
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!authClient) {
|
||||
setSession(null);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
@@ -43,40 +106,99 @@ export const AuthProvider: ParentComponent = (props) => {
|
||||
})();
|
||||
});
|
||||
|
||||
const requireNeonAuth = () => {
|
||||
if (!authClient) {
|
||||
throw new Error("Neon Auth is not configured for this environment.");
|
||||
}
|
||||
return authClient;
|
||||
};
|
||||
|
||||
const value: AuthContextValue = {
|
||||
session,
|
||||
loading,
|
||||
usesNeonAuth: () => Boolean(authClient),
|
||||
supportsMagicLink: () => false,
|
||||
supportsGoogleSignIn: () => Boolean(authClient),
|
||||
async getToken() {
|
||||
if (!authClient) return null;
|
||||
const demoToken = localStorage.getItem(localAuthTokenKey);
|
||||
if (demoToken?.startsWith("demo.")) {
|
||||
return demoToken;
|
||||
}
|
||||
if (!authClient) {
|
||||
return session()?.session?.token ?? null;
|
||||
}
|
||||
const jwtToken = await (
|
||||
authClient as unknown as { getJWTToken?: () => Promise<string | null | undefined> }
|
||||
).getJWTToken?.();
|
||||
if (typeof jwtToken === "string" && jwtToken.trim() !== "") {
|
||||
return jwtToken;
|
||||
}
|
||||
return session()?.session?.token ?? null;
|
||||
},
|
||||
async signInDemo() {
|
||||
if (!authClient) {
|
||||
setSession({
|
||||
user: {
|
||||
id: "demo-owner",
|
||||
email: "owner@bookra.dev",
|
||||
name: "Bookra Demo Owner",
|
||||
},
|
||||
session: {
|
||||
id: "demo-session",
|
||||
userId: "demo-owner",
|
||||
expiresAt: new Date(Date.now() + 60 * 60 * 1000),
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
await authClient.signIn.email({
|
||||
email: "owner@bookra.dev",
|
||||
password: "bookra-demo-password",
|
||||
async signUpWithEmail(name: string, email: string, password: string) {
|
||||
const client = requireNeonAuth();
|
||||
await client.signUp.email({
|
||||
email,
|
||||
password,
|
||||
name,
|
||||
});
|
||||
const response = await authClient.getSession();
|
||||
const response = await client.getSession();
|
||||
setSession((response?.data as unknown as AuthSession | undefined) ?? null);
|
||||
},
|
||||
async signInWithEmail(email: string, password: string) {
|
||||
const client = requireNeonAuth();
|
||||
await client.signIn.email({
|
||||
email,
|
||||
password,
|
||||
});
|
||||
const response = await client.getSession();
|
||||
setSession((response?.data as unknown as AuthSession | undefined) ?? null);
|
||||
},
|
||||
async signInAsDemo() {
|
||||
const demoToken =
|
||||
"demo." +
|
||||
btoa(
|
||||
JSON.stringify({
|
||||
sub: "demo-user-id",
|
||||
email: "demo@bookra.io",
|
||||
name: "Demo User",
|
||||
exp: Math.floor(Date.now() / 1000) + 86400,
|
||||
iat: Math.floor(Date.now() / 1000),
|
||||
demo: true,
|
||||
}),
|
||||
).replace(/=/g, "") +
|
||||
".demo-signature";
|
||||
|
||||
const demoUser: LocalUser = {
|
||||
id: "demo-user-id",
|
||||
email: "demo@bookra.io",
|
||||
name: "Demo User",
|
||||
};
|
||||
|
||||
localStorage.setItem(localAuthTokenKey, demoToken);
|
||||
setSession(sessionFromLocalToken(demoToken, demoUser));
|
||||
},
|
||||
async sendMagicLink() {
|
||||
throw new Error("Magic link sign-in is not used in this app.");
|
||||
},
|
||||
async signInWithMagicLink() {
|
||||
throw new Error("Magic link callback is not used in this app.");
|
||||
},
|
||||
async signInWithGoogle() {
|
||||
const client = requireNeonAuth();
|
||||
await client.signIn.social({
|
||||
provider: "google",
|
||||
callbackURL: `${window.location.origin}/dashboard`,
|
||||
});
|
||||
},
|
||||
async signOut() {
|
||||
if (!authClient) return;
|
||||
await authClient.signOut();
|
||||
setSession(null);
|
||||
try {
|
||||
if (authClient) {
|
||||
await authClient.signOut();
|
||||
}
|
||||
} finally {
|
||||
clearLocalSession();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -10,82 +10,576 @@ import type { Locale } from "@bookra/shared-types";
|
||||
|
||||
const dictionaries = {
|
||||
cs: {
|
||||
// Navigation & Auth
|
||||
"nav.booking": "Veřejná rezervace",
|
||||
"nav.dashboard": "Aplikace",
|
||||
"auth.signIn": "Přihlásit",
|
||||
"auth.signOut": "Odhlásit",
|
||||
"home.eyebrow": "Bookra",
|
||||
"home.title": "Klidný rezervační software pro lokální služby.",
|
||||
"home.body":
|
||||
"Root je marketingový vstup do produktu. Hlavní aplikace začíná v dashboardu a veřejná rezervace zůstává oddělená pro zákazníky.",
|
||||
"home.primary": "Otevřít aplikaci",
|
||||
"home.secondary": "Zobrazit veřejnou rezervaci",
|
||||
"home.appLabel": "Hlavní vstup",
|
||||
"home.appTitle": "/dashboard",
|
||||
"home.appBody":
|
||||
"Majitelé a tým pokračují přímo do aplikace, kde řeší dashboard, billing, tenant bootstrap a provoz.",
|
||||
"home.publicLabel": "Veřejný tok",
|
||||
"home.publicTitle": "/book/:tenantSlug",
|
||||
"home.publicBody":
|
||||
"Customer-facing booking flow zůstává mimo aplikaci, aby byl rychlý, čistý a bez interního šumu.",
|
||||
"dashboard.title": "Owner dashboard",
|
||||
"dashboard.body":
|
||||
"Track weekly bookings, cancellations, utilization, and subscription state with a tenant-aware shell ready for Neon-backed data.",
|
||||
"dashboard.kpi.bookings": "Bookings this week",
|
||||
"dashboard.kpi.cancellations": "Cancellations",
|
||||
"dashboard.kpi.utilization": "Utilization",
|
||||
"dashboard.authRequired": "Live dashboard data needs a Neon Auth session and JWT.",
|
||||
"dashboard.bootstrap": "Tenant bootstrap",
|
||||
"dashboard.previewMode": "Preview mode",
|
||||
"dashboard.billing": "Billing",
|
||||
"dashboard.checkout": "Open checkout",
|
||||
"dashboard.refreshBilling": "Refresh billing",
|
||||
"dashboard.plan": "Plan",
|
||||
"dashboard.status": "Status",
|
||||
"dashboard.entitlements": "Entitlements",
|
||||
"nav.about": "O nás",
|
||||
"nav.contact": "Kontakt",
|
||||
|
||||
// Calendar
|
||||
"calendar.months.0": "Leden",
|
||||
"calendar.months.1": "Únor",
|
||||
"calendar.months.2": "Březen",
|
||||
"calendar.months.3": "Duben",
|
||||
"calendar.months.4": "Květen",
|
||||
"calendar.months.5": "Červen",
|
||||
"calendar.months.6": "Červenec",
|
||||
"calendar.months.7": "Srpen",
|
||||
"calendar.months.8": "Září",
|
||||
"calendar.months.9": "Říjen",
|
||||
"calendar.months.10": "Listopad",
|
||||
"calendar.months.11": "Prosinec",
|
||||
"calendar.days.0": "Po",
|
||||
"calendar.days.1": "Út",
|
||||
"calendar.days.2": "St",
|
||||
"calendar.days.3": "Čt",
|
||||
"calendar.days.4": "Pá",
|
||||
"calendar.days.5": "So",
|
||||
"calendar.days.6": "Ne",
|
||||
"calendar.prevMonth": "Předchozí měsíc",
|
||||
"calendar.nextMonth": "Další měsíc",
|
||||
"common.cancel": "Zrušit",
|
||||
"auth.signIn": "Přihlásit se",
|
||||
"auth.signOut": "Odhlásit se",
|
||||
"auth.signInTitle": "Přihlášení",
|
||||
"auth.signInBody": "Použijte účet vytvořený v Bookra Auth.",
|
||||
"auth.email": "E-mail",
|
||||
"auth.password": "Heslo",
|
||||
"auth.signInFailed": "Přihlášení se nepodařilo.",
|
||||
"auth.magicLinkSent": "Magický odkaz odeslán na váš e-mail",
|
||||
"auth.magicLinkTitle": "Přihlášení bez hesla",
|
||||
"auth.magicLinkBody": "Zadejte svůj e-mail a pošleme vám bezpečný odkaz pro přihlášení.",
|
||||
"auth.orContinueWith": "nebo pokračujte s",
|
||||
"auth.continueWithGoogle": "Pokračovat s Google",
|
||||
"auth.noAccount": "Nemáte účet?",
|
||||
"auth.createAccount": "Vytvořit účet",
|
||||
"auth.forgotPassword": "Zapomněli jste heslo?",
|
||||
"auth.registerTitle": "Vytvořit účet",
|
||||
"auth.registerBody": "Začněte spravovat své rezervace s Bookra.",
|
||||
"auth.fullName": "Celé jméno",
|
||||
"auth.confirmPassword": "Potvrdit heslo",
|
||||
"auth.passwordMismatch": "Hesla se neshodují",
|
||||
"auth.emailInvalid": "Zadejte platný e-mail",
|
||||
"auth.passwordTooShort": "Heslo musí mít alespoň 8 znaků",
|
||||
"auth.accountCreated": "Účet vytvořen",
|
||||
"auth.checkEmail": "Zkontrolujte svůj e-mail pro potvrzení",
|
||||
"auth.magicLink.checkInbox": "Zkontrolujte svou e-mailovou schránku",
|
||||
"auth.magicLink.instructions": "Klikněte na odkaz v e-mailu pro přihlášení. Odkaz platí 15 minut.",
|
||||
"auth.magicLink.didntReceive": "Nepřišel e-mail?",
|
||||
"auth.magicLink.resend": "Poslat znovu",
|
||||
"auth.magicLink.backToSignIn": "Zpět na přihlášení",
|
||||
"auth.errors.invalidCredentials": "Neplatné přihlašovací údaje",
|
||||
"auth.errors.emailExists": "Tento e-mail je již registrován",
|
||||
"auth.errors.tokenExpired": "Odkaz vypršel, požádejte o nový",
|
||||
"auth.errors.tokenInvalid": "Neplatný odkaz, zkuste to znovu",
|
||||
"auth.welcome": "Vítejte v Bookra",
|
||||
"auth.welcomeBody": "Děkujeme za registraci. Vaše rezervace budou nyní mnohem jednodušší.",
|
||||
|
||||
// Hero Section
|
||||
"home.badge": "Nyní zdarma pro začátečníky",
|
||||
"home.hero.title": "Klidný rezervační software pro lokální služby",
|
||||
"home.hero.subtitle": "Spravujte rezervace, zákazníky a tým na jednom místě. Bez zbytečné složitosti — jen spolehlivý systém, který funguje.",
|
||||
"home.hero.cta.primary": "Začít zdarma",
|
||||
"home.hero.cta.secondary": "Otevřít rezervaci",
|
||||
"home.trust": "Důvěřují nám podniky po celé ČR",
|
||||
|
||||
// Features Section
|
||||
"home.features.eyebrow": "Funkce",
|
||||
"home.features.title": "Vše potřebné pro váš podnik",
|
||||
"home.features.subtitle": "Od správy rezervací po e-mailová připomenutí — máte vše, co potřebujete k efektivnímu provozu.",
|
||||
|
||||
"home.feature.scheduling.title": "Chytré plánování",
|
||||
"home.feature.scheduling.desc": "Automatické detekce konfliktů, buffer mezi rezervacemi a správa více lokací.",
|
||||
"home.feature.customers.title": "Správa zákazníků",
|
||||
"home.feature.customers.desc": "Uchovávejte historii návštěv, preference a kontakty na jednom místě.",
|
||||
"home.feature.reminders.title": "Automatická připomenutí",
|
||||
"home.feature.reminders.desc": "E-mailová upozornění pro vás i zákazníky. Méně zapomenutých rezervací.",
|
||||
"home.feature.availability.title": "Flexibilní dostupnost",
|
||||
"home.feature.availability.desc": "Nastavte pracovní dobu, výjimky a black-out dny podle vašich potřeb.",
|
||||
"home.feature.quick.title": "Rychlé rezervace",
|
||||
"home.feature.quick.desc": "Zákazníci si zarezervují termín za méně než 60 sekund. Bez registrace nutné.",
|
||||
"home.feature.secure.title": "Bezpečné údaje",
|
||||
"home.feature.secure.desc": "GDPR kompatibilní, šifrovaná data, zálohy a plná kontrola nad soukromím.",
|
||||
|
||||
// How It Works
|
||||
"home.how.eyebrow": "Jak to funguje",
|
||||
"home.how.title": "Začněte za 5 minut",
|
||||
"home.how.subtitle": "Žádné složité nastavování. Vytvořte si profil, přidejte služby a začněte přijímat rezervace ještě dnes.",
|
||||
"home.how.cta": "Vytvořit účet",
|
||||
|
||||
"home.step1.title": "Vytvořte si profil",
|
||||
"home.step1.desc": "Zadejte název podniku, lokaci a kontaktní údaje. Trvá to méně než minutu.",
|
||||
"home.step2.title": "Přidejte služby a tým",
|
||||
"home.step2.desc": "Nastavte služby, ceny, pracovní dobu a pozvěte členy týmu.",
|
||||
"home.step3.title": "Spusťte rezervace",
|
||||
"home.step3.desc": "Získáte unikátní odkaz, který můžete sdílet se zákazníky nebo vložit na web.",
|
||||
"home.step4.title": "To je vše!",
|
||||
"home.step4.desc": "Začnete přijímat rezervace okamžitě.",
|
||||
|
||||
// Testimonials
|
||||
"home.testimonials.eyebrow": "Reference",
|
||||
"home.testimonials.title": "Co říkají naši zákazníci",
|
||||
|
||||
"home.testimonial1.quote": "Bookra nám ušetřila hodiny administrativy týdně. Zákazníci milují jednoduchost online rezervací.",
|
||||
"home.testimonial1.author": "Martina Nováková",
|
||||
"home.testimonial1.role": "Majitelka, Salon Ella",
|
||||
"home.testimonial2.quote": "Konec dvojitým rezervacím a zmatkům v diáři. Vše je přehledné a pod kontrolou.",
|
||||
"home.testimonial2.author": "David Svoboda",
|
||||
"home.testimonial2.role": "Fyzioterapeut, Physio Care",
|
||||
"home.testimonial3.quote": "Automatická e-mailová připomenutí snížila počet zapomenutých rezervací. Skvělá investice.",
|
||||
"home.testimonial3.author": "Jana Kovářová",
|
||||
"home.testimonial3.role": "Majitelka, Massage Studio",
|
||||
|
||||
// Pricing
|
||||
"home.pricing.eyebrow": "Ceník",
|
||||
"home.pricing.title": "Jednoduché a transparentní ceny",
|
||||
"home.pricing.subtitle": "Žádné skryté poplatky. Všechny plány zahrnují plnou podporu.",
|
||||
|
||||
"home.pricing.starter.name": "Starter",
|
||||
"home.pricing.starter.desc": "Pro jednotlivce a malé podniky",
|
||||
"home.pricing.starter.f1": "Do 50 rezervací/měsíc",
|
||||
"home.pricing.starter.f2": "1 lokace, 1 zaměstnanec",
|
||||
"home.pricing.starter.f3": "E-mailová podpora",
|
||||
"home.pricing.starter.cta": "Začít zdarma",
|
||||
"home.pricing.starter.trial": "15 dní zdarma po registraci",
|
||||
"home.pricing.perMonth": "/měsíc",
|
||||
|
||||
"home.pricing.pro.name": "Pro",
|
||||
"home.pricing.pro.desc": "Pro rostoucí podniky",
|
||||
"home.pricing.popular": "Nejoblíbenější",
|
||||
"home.pricing.pro.f1": "Neomezené rezervace",
|
||||
"home.pricing.pro.f2": "3 lokace, 10 zaměstnanců",
|
||||
"home.pricing.pro.f3": "E-mailová připomenutí",
|
||||
"home.pricing.pro.f4": "Prioritní podpora",
|
||||
"home.pricing.pro.f5": "Analytika a reporty",
|
||||
"home.pricing.pro.cta": "Začít 15denní zkoušku",
|
||||
"home.pricing.pro.trial": "15 dní zdarma po registraci",
|
||||
|
||||
"home.pricing.biz.name": "Business",
|
||||
"home.pricing.biz.desc": "Pro větší týmy a franšízy",
|
||||
"home.pricing.biz.f1": "Neomezené vše",
|
||||
"home.pricing.biz.f2": "Více lokací",
|
||||
"home.pricing.biz.f3": "API přístup",
|
||||
"home.pricing.biz.f4": "Dedikovaný manažer",
|
||||
"home.pricing.biz.cta": "Kontaktovat prodej",
|
||||
"home.pricing.biz.trial": "Individuální řešení na míru",
|
||||
|
||||
// CTA
|
||||
"home.cta.title": "Připraveni zjednodušit své rezervace?",
|
||||
"home.cta.subtitle": "Připojte se k tisícům podniků, které šetří čas s Bookra.",
|
||||
"home.cta.primary": "Začít zdarma",
|
||||
"home.cta.secondary": "Otevřít rezervaci",
|
||||
|
||||
// Widget Builder
|
||||
"widget.builder.title": "Rezervační widget",
|
||||
"widget.builder.subtitle": "Vyberte styl a zkopírujte kód na váš web",
|
||||
"widget.types.title": "Dostupné widgety",
|
||||
"widget.types.desc": "Přetáhněte pro změnu pořadí, klikněte pro výběr",
|
||||
"widget.type.iframe.title": "Inline iframe",
|
||||
"widget.type.iframe.desc": "Vložte rezervační formulář přímo do stránky",
|
||||
"widget.type.iframe.preview": "Nejlepší pro: Hlavní stránky, rezervační sekce",
|
||||
"widget.type.button.title": "Rezervační tlačítko",
|
||||
"widget.type.button.desc": "Jednoduché tlačítko odkazující na rezervaci",
|
||||
"widget.type.button.preview": "Nejlepší pro: Navigace, CTA sekce",
|
||||
"widget.type.inline.title": "Inline kalendář",
|
||||
"widget.type.inline.desc": "Interaktivní kalendář přímo na vašem webu",
|
||||
"widget.type.inline.preview": "Nejlepší pro: Stránky služeb, produkty",
|
||||
"widget.type.modal.title": "Modal popup",
|
||||
"widget.type.modal.desc": "Rezervace se otevře v překrývacím okně",
|
||||
"widget.type.modal.preview": "Nejlepší pro: Rychlý přístup bez opuštění stránky",
|
||||
"widget.type.floating.title": "Plovoucí bublina",
|
||||
"widget.type.floating.desc": "Plovoucí tlačítko v rohu obrazovky",
|
||||
"widget.type.floating.preview": "Nejlepší pro: E-shopy, kontinuální dostupnost",
|
||||
"widget.button.text": "Rezervovat termín",
|
||||
"widget.modal.trigger": "Otevřít rezervaci",
|
||||
"widget.styling.title": "Vzhled",
|
||||
"widget.styling.color": "Primární barva",
|
||||
"widget.styling.radius": "Zaoblení rohů",
|
||||
"widget.styling.shadow": "Intenzita stínu",
|
||||
"widget.styling.buttonText": "Text tlačítka",
|
||||
"widget.styling.position": "Pozice",
|
||||
"widget.position.topLeft": "Vlevo nahoře",
|
||||
"widget.position.topRight": "Vpravo nahoře",
|
||||
"widget.position.bottomLeft": "Vlevo dole",
|
||||
"widget.position.bottomRight": "Vpravo dole",
|
||||
"widget.preview.show": "Zobrazit náhled",
|
||||
"widget.preview.hide": "Skrýt náhled",
|
||||
"widget.customize.title": "Přizpůsobení vzhledu",
|
||||
"widget.customize.desc": "Vyberte téma a velikost widgetu",
|
||||
"widget.theme.label": "Téma",
|
||||
"widget.theme.light": "Světlé",
|
||||
"widget.theme.dark": "Tmavé",
|
||||
"widget.theme.auto": "Automatické",
|
||||
"widget.size.label": "Velikost",
|
||||
"widget.size.compact": "Kompaktní",
|
||||
"widget.size.default": "Standardní",
|
||||
"widget.size.full": "Plná šířka",
|
||||
"widget.code.title": "Kód widgetu",
|
||||
"widget.code.desc": "Zkopírujte kód a vložte ho na váš web",
|
||||
"widget.install.title": "Jak nainstalovat",
|
||||
"widget.install.desc": "Vložte kód do HTML vaší stránky tam, kde chcete zobrazit widget.",
|
||||
"widget.install.preview": "Náhled živé stránky",
|
||||
"widget.security.title": "Bezpečnost",
|
||||
"widget.security.desc": "Všechny widgety jsou zabezpečeny a izolovány:",
|
||||
"widget.security.https": "Šifrovaná HTTPS komunikace",
|
||||
"widget.security.isolation": "Sandbox izolace iframe",
|
||||
"widget.security.cors": "CORS ochrana API",
|
||||
"common.copy": "Kopírovat",
|
||||
"common.copied": "Zkopírováno!",
|
||||
|
||||
// Footer
|
||||
"footer.copyright": "© 2026 Bookra. Všechna práva vyhrazena.",
|
||||
"footer.privacy": "Ochrana soukromí",
|
||||
"footer.terms": "Podmínky použití",
|
||||
"footer.description": "Klidný rezervační software pro lokální služby. Spravujte rezervace, zákazníky a tým na jednom místě.",
|
||||
"footer.links.title": "Navigace",
|
||||
"footer.legal.title": "Právní informace",
|
||||
|
||||
// Dashboard (existing)
|
||||
"dashboard.title": "Přehled podniku",
|
||||
"dashboard.body": "Sledujte rezervace, nastavení, předplatné a rezervační widget na jednom místě.",
|
||||
"dashboard.kpi.bookings": "Rezervace tento týden",
|
||||
"dashboard.kpi.cancellations": "Zrušení",
|
||||
"dashboard.kpi.utilization": "Vytížení",
|
||||
"dashboard.welcome.title": "Vítejte v Bookra",
|
||||
"dashboard.welcome.body": "Zjednodušte své rezervace a mějte více času na to, co vás baví.",
|
||||
"dashboard.authRequired": "Pro vstup do aplikace se přihlaste nebo si vytvořte účet.",
|
||||
"dashboard.bootstrap": "Vytvoření prostoru",
|
||||
"dashboard.liveData": "Živá data",
|
||||
"dashboard.liveDataBody": "Dashboard, nastavení a předplatné se načítají z API pro přihlášený účet.",
|
||||
"dashboard.apiReady": "API připojení aktivní",
|
||||
"dashboard.billing": "Předplatné",
|
||||
"dashboard.checkout": "Otevřít platbu",
|
||||
"dashboard.refreshBilling": "Obnovit předplatné",
|
||||
"dashboard.plan": "Plán",
|
||||
"dashboard.status": "Stav",
|
||||
"dashboard.entitlements": "Funkce a limity",
|
||||
"dashboard.onboarding.title": "Vytvořit pracovní prostor",
|
||||
"dashboard.onboarding.body":
|
||||
"Tento účet ještě nemá tenant membership. Vytvořte první workspace a pokračujte do aplikace.",
|
||||
"dashboard.onboarding.body": "Tento účet ještě nemá vytvořený pracovní prostor. Vytvořte první prostor a pokračujte do aplikace.",
|
||||
"dashboard.onboarding.name": "Název firmy",
|
||||
"dashboard.onboarding.slug": "Slug",
|
||||
"dashboard.onboarding.preset": "Preset",
|
||||
"dashboard.onboarding.locale": "Locale",
|
||||
"dashboard.onboarding.timezone": "Timezone",
|
||||
"dashboard.onboarding.submit": "Vytvořit workspace",
|
||||
"dashboard.onboarding.pending": "Vytvářím workspace...",
|
||||
"booking.title": "Public booking page",
|
||||
"booking.body":
|
||||
"The public experience stays light: availability, slot confirmation, and a clear fallback for guest booking or account-based booking later.",
|
||||
"booking.empty": "Live availability will load from the Railway API when the tenant is configured.",
|
||||
"dashboard.onboarding.slug": "Identifikátor (URL)",
|
||||
"dashboard.onboarding.preset": "Typ podnikání",
|
||||
"dashboard.onboarding.locale": "Jazyk",
|
||||
"dashboard.onboarding.timezone": "Časové pásmo",
|
||||
"dashboard.onboarding.submit": "Vytvořit prostor",
|
||||
"dashboard.onboarding.pending": "Vytvářím prostor...",
|
||||
"booking.title": "Rezervace",
|
||||
"booking.body": "Vyberte dostupný termín, doplňte kontaktní údaje a potvrzení přijde e-mailem.",
|
||||
"booking.slots": "Dostupné termíny",
|
||||
"booking.selectTime": "Vyberte čas",
|
||||
"booking.selectTimeBody": "Vyberte si z dostupných termínů pro služby a lekce.",
|
||||
"booking.empty": "Momentálně nejsou k dispozici žádné termíny.",
|
||||
"booking.emptyHint": "Zkuste to prosím později.",
|
||||
"booking.only": "Zbývají",
|
||||
"booking.left": "místa",
|
||||
"booking.spotsAvailable": "volná místa",
|
||||
"booking.submitting": "Rezervuji...",
|
||||
"booking.submit": "Rezervovat",
|
||||
"booking.business": "Podnik",
|
||||
"booking.sidebar.body": "Rezervace proběhne online. Potvrzení a připomenutí přijdou e-mailem.",
|
||||
"booking.customer.title": "Kontaktní údaje",
|
||||
"booking.customer.body": "Tyto údaje použijeme pro potvrzení rezervace a připomenutí.",
|
||||
"booking.customer.name": "Jméno",
|
||||
"booking.customer.email": "E-mail",
|
||||
"booking.customer.notes": "Poznámka",
|
||||
"booking.customerRequired": "Před rezervací vyplňte jméno a e-mail.",
|
||||
"booking.failed": "Rezervaci se nepodařilo vytvořit",
|
||||
"booking.created": "Rezervace vytvořena",
|
||||
"booking.confirmed": "Rezervace potvrzena",
|
||||
"booking.help.title": "Potřebujete pomoc?",
|
||||
"booking.help.body": "Ozvěte se provozovateli služby.",
|
||||
"booking.expect.title": "Co můžete čekat",
|
||||
"booking.expect.confirmation": "Okamžité potvrzení rezervace",
|
||||
"booking.expect.reminders": "E-mailové připomenutí před návštěvou",
|
||||
"booking.expect.rescheduling": "Jednoduchá domluva změny termínu",
|
||||
|
||||
// About Page
|
||||
"about.title": "O nás",
|
||||
"about.subtitle": "Jsme tým, který věří, že správa rezervací může být jednoduchá a příjemná. Naše mise je pomáhat lokálním podnikům růst.",
|
||||
"about.story.title": "Náš příběh",
|
||||
"about.story.p1": "Bookra vznikla z vlastní potřeby. Jako majitelé malých podniků jsme byli frustrovaní z komplikovaných rezervačních systémů, které byly plné funkcí, které nikdo nepoužívá.",
|
||||
"about.story.p2": "Rozhodli jsme se vytvořit něco jiného — software, který je klidný, intuitivní a dělá přesně to, co potřebujete. Bez zbytečné složitosti.",
|
||||
"about.story.p3": "Dnes pomáháme tisícům podniků v České republice a Evropě zjednodušit jejich rezervace a šetřit čas. A to je jen začátek.",
|
||||
"about.values.title": "Naše hodnoty",
|
||||
"about.values.subtitle": "To, co nás řídí při vývoji Bookra",
|
||||
"about.values.simple.title": "Jednoduchost",
|
||||
"about.values.simple.desc": "Méně je více. Každá funkce musí mít jasný účel a přinášet hodnotu.",
|
||||
"about.values.reliable.title": "Spolehlivost",
|
||||
"about.values.reliable.desc": "Vaše data jsou v bezpečí a systém funguje, když ho potřebujete.",
|
||||
"about.values.local.title": "Lokální přístup",
|
||||
"about.values.local.desc": "Rozumíme českému a evropskému trhu. Podporujeme lokální podniky.",
|
||||
"about.team.title": "Tým Bookra",
|
||||
"about.team.subtitle": "Za Bookra stojí malý, ale oddaný tým. Každý z nás pečuje o to, aby software dělal to, co má.",
|
||||
"about.cta": "Kontaktujte nás",
|
||||
|
||||
// Contact Page
|
||||
"contact.title": "Kontakt",
|
||||
"contact.subtitle": "Máte dotaz nebo zpětnou vazbu? Rádi od vás uslyšíme. Vyplňte formulář a ozveme se vám.",
|
||||
"contact.form.title": "Napište nám",
|
||||
"contact.form.name": "Jméno",
|
||||
"contact.form.email": "E-mail",
|
||||
"contact.form.message": "Zpráva",
|
||||
"contact.form.submit": "Odeslat zprávu",
|
||||
"contact.success.title": "Zpráva odeslána",
|
||||
"contact.success.body": "Děkujeme za váš zájem. Ozveme se vám co nejdříve.",
|
||||
"contact.info.email.title": "E-mail",
|
||||
"contact.info.email.desc": "Preferujete psát? Jsme tu pro vás.",
|
||||
"contact.info.hours.title": "Pracovní doba",
|
||||
"contact.info.hours.desc": "Odpovídáme během pracovních dní 9:00 — 17:00 CET.",
|
||||
|
||||
// Legal
|
||||
"legal.privacy.title": "Ochrana soukromí",
|
||||
"legal.privacy.body": "Bookra zpracovává údaje potřebné pro vytvoření rezervace, správu účtu, připomenutí a zabezpečení služby. Údaje tenantů jsou oddělené a přístup k nim je omezen podle role uživatele.",
|
||||
"legal.privacy.data.title": "Jaké údaje zpracováváme",
|
||||
"legal.privacy.data.body": "Kontaktní údaje zákazníků, čas rezervace, poznámky zadané při rezervaci, údaje o účtu provozovatele a technické záznamy potřebné pro bezpečný provoz.",
|
||||
"legal.privacy.rights.title": "Práva a žádosti",
|
||||
"legal.privacy.rights.body": "Žádosti o přístup, opravu nebo výmaz údajů řeší provozovatel konkrétního účtu. Bookra poskytuje technické prostředky pro bezpečné zpracování.",
|
||||
"legal.terms.title": "Podmínky použití",
|
||||
"legal.terms.body": "Bookra je software pro správu rezervací lokálních služeb. Provozovatel účtu odpovídá za správnost nabídky, dostupnost termínů a komunikaci se zákazníky.",
|
||||
"legal.terms.service.title": "Používání služby",
|
||||
"legal.terms.service.body": "Službu je nutné používat v souladu se zákonem, bez zneužití rezervačních formulářů, obcházení zabezpečení nebo nahrávání zakázaného obsahu.",
|
||||
"legal.terms.billing.title": "Předplatné",
|
||||
"legal.terms.billing.body": "Placené plány se účtují přes Paddle. Aktivní plán určuje dostupné limity, rozšíření a podpůrné funkce.",
|
||||
},
|
||||
en: {
|
||||
// Navigation & Auth
|
||||
"nav.booking": "Public booking",
|
||||
"nav.dashboard": "App",
|
||||
"nav.about": "About us",
|
||||
"nav.contact": "Contact",
|
||||
|
||||
// Calendar
|
||||
"calendar.months.0": "January",
|
||||
"calendar.months.1": "February",
|
||||
"calendar.months.2": "March",
|
||||
"calendar.months.3": "April",
|
||||
"calendar.months.4": "May",
|
||||
"calendar.months.5": "June",
|
||||
"calendar.months.6": "July",
|
||||
"calendar.months.7": "August",
|
||||
"calendar.months.8": "September",
|
||||
"calendar.months.9": "October",
|
||||
"calendar.months.10": "November",
|
||||
"calendar.months.11": "December",
|
||||
"calendar.days.0": "Mon",
|
||||
"calendar.days.1": "Tue",
|
||||
"calendar.days.2": "Wed",
|
||||
"calendar.days.3": "Thu",
|
||||
"calendar.days.4": "Fri",
|
||||
"calendar.days.5": "Sat",
|
||||
"calendar.days.6": "Sun",
|
||||
"calendar.prevMonth": "Previous month",
|
||||
"calendar.nextMonth": "Next month",
|
||||
"common.cancel": "Cancel",
|
||||
"auth.signIn": "Sign in",
|
||||
"auth.signOut": "Sign out",
|
||||
"home.eyebrow": "Bookra",
|
||||
"home.title": "Calm booking software for local service businesses.",
|
||||
"home.body":
|
||||
"The root is the marketing entry to the product. The main app starts in the dashboard, while public booking stays separate for customers.",
|
||||
"home.primary": "Open app",
|
||||
"home.secondary": "View public booking",
|
||||
"home.appLabel": "Main app entry",
|
||||
"home.appTitle": "/dashboard",
|
||||
"home.appBody":
|
||||
"Owners and staff move directly into the app for dashboard, billing, tenant bootstrap, and day-to-day operations.",
|
||||
"home.publicLabel": "Public flow",
|
||||
"home.publicTitle": "/book/:tenantSlug",
|
||||
"home.publicBody":
|
||||
"The customer-facing booking flow stays outside the app so it remains focused, fast, and free of internal noise.",
|
||||
"auth.signInTitle": "Sign in",
|
||||
"auth.signInBody": "Use the account created for your Bookra workspace.",
|
||||
"auth.email": "Email",
|
||||
"auth.password": "Password",
|
||||
"auth.signInFailed": "Sign in failed.",
|
||||
"auth.magicLinkSent": "Magic link sent to your email",
|
||||
"auth.magicLinkTitle": "Passwordless sign-in",
|
||||
"auth.magicLinkBody": "Enter your email and we'll send you a secure sign-in link.",
|
||||
"auth.orContinueWith": "or continue with",
|
||||
"auth.continueWithGoogle": "Continue with Google",
|
||||
"auth.noAccount": "Don't have an account?",
|
||||
"auth.createAccount": "Create account",
|
||||
"auth.forgotPassword": "Forgot password?",
|
||||
"auth.registerTitle": "Create account",
|
||||
"auth.registerBody": "Start managing your bookings with Bookra.",
|
||||
"auth.fullName": "Full name",
|
||||
"auth.confirmPassword": "Confirm password",
|
||||
"auth.passwordMismatch": "Passwords do not match",
|
||||
"auth.emailInvalid": "Please enter a valid email",
|
||||
"auth.passwordTooShort": "Password must be at least 8 characters",
|
||||
"auth.accountCreated": "Account created",
|
||||
"auth.checkEmail": "Check your email for confirmation",
|
||||
"auth.magicLink.checkInbox": "Check your inbox",
|
||||
"auth.magicLink.instructions": "Click the link in the email to sign in. The link is valid for 15 minutes.",
|
||||
"auth.magicLink.didntReceive": "Didn't receive the email?",
|
||||
"auth.magicLink.resend": "Resend",
|
||||
"auth.magicLink.backToSignIn": "Back to sign in",
|
||||
"auth.errors.invalidCredentials": "Invalid credentials",
|
||||
"auth.errors.emailExists": "This email is already registered",
|
||||
"auth.errors.tokenExpired": "Link expired, please request a new one",
|
||||
"auth.errors.tokenInvalid": "Invalid link, please try again",
|
||||
"auth.welcome": "Welcome to Bookra",
|
||||
"auth.welcomeBody": "Thanks for signing up. Your bookings just got a whole lot easier.",
|
||||
|
||||
// Hero Section
|
||||
"home.badge": "Now free for starters",
|
||||
"home.hero.title": "Calm booking software for local services",
|
||||
"home.hero.subtitle": "Manage bookings, customers, and your team in one place. No unnecessary complexity — just a reliable system that works.",
|
||||
"home.hero.cta.primary": "Get started free",
|
||||
"home.hero.cta.secondary": "Open booking page",
|
||||
"home.trust": "Trusted by businesses across Europe",
|
||||
|
||||
// Features Section
|
||||
"home.features.eyebrow": "Features",
|
||||
"home.features.title": "Everything your business needs",
|
||||
"home.features.subtitle": "From booking management to email reminders, you have what you need to run your business efficiently.",
|
||||
|
||||
"home.feature.scheduling.title": "Smart Scheduling",
|
||||
"home.feature.scheduling.desc": "Automatic conflict detection, buffer times between bookings, and multi-location support.",
|
||||
"home.feature.customers.title": "Customer Management",
|
||||
"home.feature.customers.desc": "Keep visit history, preferences, and contact details in one organized place.",
|
||||
"home.feature.reminders.title": "Automated Reminders",
|
||||
"home.feature.reminders.desc": "Email notifications for you and your customers. Fewer no-shows, more revenue.",
|
||||
"home.feature.availability.title": "Flexible Availability",
|
||||
"home.feature.availability.desc": "Set working hours, exceptions, and blackout days according to your needs.",
|
||||
"home.feature.quick.title": "Quick Bookings",
|
||||
"home.feature.quick.desc": "Customers book in under 60 seconds. No account required for guest bookings.",
|
||||
"home.feature.secure.title": "Secure Data",
|
||||
"home.feature.secure.desc": "GDPR compliant, encrypted data, backups, and full privacy control.",
|
||||
|
||||
// How It Works
|
||||
"home.how.eyebrow": "How it works",
|
||||
"home.how.title": "Get started in 5 minutes",
|
||||
"home.how.subtitle": "No complex setup. Create your profile, add services, and start accepting bookings today.",
|
||||
"home.how.cta": "Create account",
|
||||
|
||||
"home.step1.title": "Create your profile",
|
||||
"home.step1.desc": "Enter your business name, location, and contact details. Takes less than a minute.",
|
||||
"home.step2.title": "Add services & team",
|
||||
"home.step2.desc": "Set up your service menu, pricing, working hours, and invite team members.",
|
||||
"home.step3.title": "Start taking bookings",
|
||||
"home.step3.desc": "Get a unique booking link to share with customers or embed on your website.",
|
||||
"home.step4.title": "That's it!",
|
||||
"home.step4.desc": "Start accepting bookings immediately.",
|
||||
|
||||
// Testimonials
|
||||
"home.testimonials.eyebrow": "Testimonials",
|
||||
"home.testimonials.title": "What our customers say",
|
||||
|
||||
"home.testimonial1.quote": "Bookra saved us hours of admin work every week. Customers love how easy online booking is.",
|
||||
"home.testimonial1.author": "Sarah Mitchell",
|
||||
"home.testimonial1.role": "Owner, Studio Ella",
|
||||
"home.testimonial2.quote": "No more double bookings and diary confusion. Everything is clear and under control.",
|
||||
"home.testimonial2.author": "James Chen",
|
||||
"home.testimonial2.role": "Physiotherapist, Physio Care",
|
||||
"home.testimonial3.quote": "Automatic email reminders reduced forgotten bookings. Great investment for our business.",
|
||||
"home.testimonial3.author": "Emma Wilson",
|
||||
"home.testimonial3.role": "Owner, Massage Studio",
|
||||
|
||||
// Pricing
|
||||
"home.pricing.eyebrow": "Pricing",
|
||||
"home.pricing.title": "Simple, transparent pricing",
|
||||
"home.pricing.subtitle": "No hidden fees. All plans include full support.",
|
||||
|
||||
"home.pricing.starter.name": "Starter",
|
||||
"home.pricing.starter.desc": "For individuals and small businesses",
|
||||
"home.pricing.starter.f1": "Up to 50 bookings/month",
|
||||
"home.pricing.starter.f2": "1 location, 1 staff member",
|
||||
"home.pricing.starter.f3": "Email support",
|
||||
"home.pricing.starter.cta": "Start for free",
|
||||
"home.pricing.starter.trial": "15 days free after sign-up",
|
||||
"home.pricing.perMonth": "/mo",
|
||||
|
||||
"home.pricing.pro.name": "Pro",
|
||||
"home.pricing.pro.desc": "For growing businesses",
|
||||
"home.pricing.popular": "Most Popular",
|
||||
"home.pricing.pro.f1": "Unlimited bookings",
|
||||
"home.pricing.pro.f2": "3 locations, 10 staff",
|
||||
"home.pricing.pro.f3": "Email reminders",
|
||||
"home.pricing.pro.f4": "Priority support",
|
||||
"home.pricing.pro.f5": "Analytics & reports",
|
||||
"home.pricing.pro.cta": "Start 15-day trial",
|
||||
"home.pricing.pro.trial": "15 days free after sign-up",
|
||||
|
||||
"home.pricing.biz.name": "Business",
|
||||
"home.pricing.biz.desc": "For larger teams and franchises",
|
||||
"home.pricing.biz.f1": "Unlimited everything",
|
||||
"home.pricing.biz.f2": "Multiple locations",
|
||||
"home.pricing.biz.f3": "API access",
|
||||
"home.pricing.biz.f4": "Dedicated manager",
|
||||
"home.pricing.biz.cta": "Contact sales",
|
||||
"home.pricing.biz.trial": "Custom enterprise solutions",
|
||||
|
||||
// CTA
|
||||
"home.cta.title": "Ready to simplify your bookings?",
|
||||
"home.cta.subtitle": "Join thousands of businesses saving time with Bookra.",
|
||||
"home.cta.primary": "Start for free",
|
||||
"home.cta.secondary": "Open booking page",
|
||||
|
||||
// Widget Builder
|
||||
"widget.builder.title": "Booking Widget",
|
||||
"widget.builder.subtitle": "Choose a style and copy the code to your website",
|
||||
"widget.types.title": "Available Widgets",
|
||||
"widget.types.desc": "Drag to reorder, click to select",
|
||||
"widget.type.iframe.title": "Inline iframe",
|
||||
"widget.type.iframe.desc": "Embed the booking form directly on your page",
|
||||
"widget.type.iframe.preview": "Best for: Homepages, booking sections",
|
||||
"widget.type.button.title": "Booking button",
|
||||
"widget.type.button.desc": "Simple button linking to your booking page",
|
||||
"widget.type.button.preview": "Best for: Navigation, CTA sections",
|
||||
"widget.type.inline.title": "Inline calendar",
|
||||
"widget.type.inline.desc": "Interactive calendar directly on your website",
|
||||
"widget.type.inline.preview": "Best for: Service pages, products",
|
||||
"widget.type.modal.title": "Modal popup",
|
||||
"widget.type.modal.desc": "Booking opens in an overlay window",
|
||||
"widget.type.modal.preview": "Best for: Quick access without leaving the page",
|
||||
"widget.type.floating.title": "Floating bubble",
|
||||
"widget.type.floating.desc": "Floating button in the corner of the screen",
|
||||
"widget.type.floating.preview": "Best for: E-commerce, continuous availability",
|
||||
"widget.button.text": "Book appointment",
|
||||
"widget.modal.trigger": "Open booking",
|
||||
"widget.styling.title": "Appearance",
|
||||
"widget.styling.color": "Primary Color",
|
||||
"widget.styling.radius": "Border Radius",
|
||||
"widget.styling.shadow": "Shadow Intensity",
|
||||
"widget.styling.buttonText": "Button Text",
|
||||
"widget.styling.position": "Position",
|
||||
"widget.position.topLeft": "Top Left",
|
||||
"widget.position.topRight": "Top Right",
|
||||
"widget.position.bottomLeft": "Bottom Left",
|
||||
"widget.position.bottomRight": "Bottom Right",
|
||||
"widget.preview.show": "Show Preview",
|
||||
"widget.preview.hide": "Hide Preview",
|
||||
"widget.customize.title": "Customize appearance",
|
||||
"widget.customize.desc": "Select theme and widget size",
|
||||
"widget.theme.label": "Theme",
|
||||
"widget.theme.light": "Light",
|
||||
"widget.theme.dark": "Dark",
|
||||
"widget.theme.auto": "Auto",
|
||||
"widget.size.label": "Size",
|
||||
"widget.size.compact": "Compact",
|
||||
"widget.size.default": "Default",
|
||||
"widget.size.full": "Full width",
|
||||
"widget.code.title": "Widget code",
|
||||
"widget.code.desc": "Copy the code and paste it into your website",
|
||||
"widget.install.title": "How to install",
|
||||
"widget.install.desc": "Paste the code into your page HTML where you want the widget to appear.",
|
||||
"widget.install.preview": "Preview live page",
|
||||
"widget.security.title": "Security",
|
||||
"widget.security.desc": "All widgets are secured and isolated:",
|
||||
"widget.security.https": "Encrypted HTTPS communication",
|
||||
"widget.security.isolation": "Sandbox iframe isolation",
|
||||
"widget.security.cors": "API CORS protection",
|
||||
"common.copy": "Copy",
|
||||
"common.copied": "Copied!",
|
||||
|
||||
// Footer
|
||||
"footer.copyright": "© 2026 Bookra. All rights reserved.",
|
||||
"footer.privacy": "Privacy",
|
||||
"footer.terms": "Terms",
|
||||
"footer.description": "Calm booking software for local services. Manage bookings, customers, and your team in one place.",
|
||||
"footer.links.title": "Navigation",
|
||||
"footer.legal.title": "Legal",
|
||||
|
||||
// Dashboard (existing)
|
||||
"dashboard.title": "Owner dashboard",
|
||||
"dashboard.body":
|
||||
"Track weekly bookings, cancellations, utilization, and subscription state with a tenant-aware shell ready for Neon-backed data.",
|
||||
"dashboard.body": "Track weekly bookings, cancellations, utilization, and subscription state with a tenant-aware shell ready for Neon-backed data.",
|
||||
"dashboard.kpi.bookings": "Bookings this week",
|
||||
"dashboard.kpi.cancellations": "Cancellations",
|
||||
"dashboard.kpi.utilization": "Utilization",
|
||||
"dashboard.welcome.title": "Welcome to Bookra",
|
||||
"dashboard.welcome.body": "Simplify your bookings and spend more time doing what you love.",
|
||||
"dashboard.authRequired": "Live dashboard data needs a Neon Auth session and JWT.",
|
||||
"dashboard.bootstrap": "Tenant bootstrap",
|
||||
"dashboard.previewMode": "Preview mode",
|
||||
"dashboard.liveData": "Live data",
|
||||
"dashboard.liveDataBody": "Dashboard, tenant, and billing data are loaded from the API for the signed-in workspace.",
|
||||
"dashboard.apiReady": "API connection active",
|
||||
"dashboard.billing": "Billing",
|
||||
"dashboard.checkout": "Open checkout",
|
||||
"dashboard.refreshBilling": "Refresh billing",
|
||||
@@ -93,8 +587,7 @@ const dictionaries = {
|
||||
"dashboard.status": "Status",
|
||||
"dashboard.entitlements": "Entitlements",
|
||||
"dashboard.onboarding.title": "Create workspace",
|
||||
"dashboard.onboarding.body":
|
||||
"This account does not have a tenant membership yet. Create the first workspace and continue into the app.",
|
||||
"dashboard.onboarding.body": "This account does not have a tenant membership yet. Create the first workspace and continue into the app.",
|
||||
"dashboard.onboarding.name": "Business name",
|
||||
"dashboard.onboarding.slug": "Slug",
|
||||
"dashboard.onboarding.preset": "Preset",
|
||||
@@ -102,10 +595,83 @@ const dictionaries = {
|
||||
"dashboard.onboarding.timezone": "Timezone",
|
||||
"dashboard.onboarding.submit": "Create workspace",
|
||||
"dashboard.onboarding.pending": "Creating workspace...",
|
||||
"booking.title": "Public booking page",
|
||||
"booking.body":
|
||||
"The public experience stays light: availability, slot confirmation, and a clear fallback for guest booking or account-based booking later.",
|
||||
"booking.empty": "Live availability will load from the Railway API when the tenant is configured.",
|
||||
"booking.title": "Book a visit",
|
||||
"booking.body": "Choose an available time, add your contact details, and receive confirmation by email.",
|
||||
"booking.slots": "Available times",
|
||||
"booking.selectTime": "Select a time",
|
||||
"booking.selectTimeBody": "Choose from available appointment and class times.",
|
||||
"booking.empty": "No bookable times are available right now.",
|
||||
"booking.emptyHint": "Please check again later.",
|
||||
"booking.only": "Only",
|
||||
"booking.left": "left",
|
||||
"booking.spotsAvailable": "spots available",
|
||||
"booking.submitting": "Booking...",
|
||||
"booking.submit": "Book now",
|
||||
"booking.business": "Business",
|
||||
"booking.sidebar.body": "Book online. Confirmation and reminders are sent by email.",
|
||||
"booking.customer.title": "Contact details",
|
||||
"booking.customer.body": "These details are used for confirmation and reminders.",
|
||||
"booking.customer.name": "Name",
|
||||
"booking.customer.email": "Email",
|
||||
"booking.customer.notes": "Note",
|
||||
"booking.customerRequired": "Add your name and email before booking.",
|
||||
"booking.failed": "Booking failed",
|
||||
"booking.created": "Booking created",
|
||||
"booking.confirmed": "Booking confirmed",
|
||||
"booking.help.title": "Need help?",
|
||||
"booking.help.body": "Contact the service provider.",
|
||||
"booking.expect.title": "What to expect",
|
||||
"booking.expect.confirmation": "Instant booking confirmation",
|
||||
"booking.expect.reminders": "Email reminders before your visit",
|
||||
"booking.expect.rescheduling": "Simple rescheduling communication",
|
||||
|
||||
// About Page
|
||||
"about.title": "About us",
|
||||
"about.subtitle": "We're a team that believes booking management can be simple and pleasant. Our mission is to help local businesses grow.",
|
||||
"about.story.title": "Our story",
|
||||
"about.story.p1": "Bookra was born from our own need. As small business owners, we were frustrated with complicated booking systems full of features nobody uses.",
|
||||
"about.story.p2": "We decided to create something different — software that is calm, intuitive, and does exactly what you need. Without unnecessary complexity.",
|
||||
"about.story.p3": "Today we help thousands of businesses in the Czech Republic and Europe simplify their bookings and save time. And this is just the beginning.",
|
||||
"about.values.title": "Our values",
|
||||
"about.values.subtitle": "What guides us in developing Bookra",
|
||||
"about.values.simple.title": "Simplicity",
|
||||
"about.values.simple.desc": "Less is more. Every feature must have a clear purpose and bring value.",
|
||||
"about.values.reliable.title": "Reliability",
|
||||
"about.values.reliable.desc": "Your data is safe and the system works when you need it.",
|
||||
"about.values.local.title": "Local approach",
|
||||
"about.values.local.desc": "We understand the Czech and European market. We support local businesses.",
|
||||
"about.team.title": "The Bookra team",
|
||||
"about.team.subtitle": "Behind Bookra is a small but dedicated team. Each of us cares that the software does what it should.",
|
||||
"about.cta": "Contact us",
|
||||
|
||||
// Contact Page
|
||||
"contact.title": "Contact",
|
||||
"contact.subtitle": "Have a question or feedback? We'd love to hear from you. Fill out the form and we'll get back to you.",
|
||||
"contact.form.title": "Write to us",
|
||||
"contact.form.name": "Name",
|
||||
"contact.form.email": "Email",
|
||||
"contact.form.message": "Message",
|
||||
"contact.form.submit": "Send message",
|
||||
"contact.success.title": "Message sent",
|
||||
"contact.success.body": "Thank you for your interest. We'll get back to you as soon as possible.",
|
||||
"contact.info.email.title": "Email",
|
||||
"contact.info.email.desc": "Prefer to write? We're here for you.",
|
||||
"contact.info.hours.title": "Working hours",
|
||||
"contact.info.hours.desc": "We respond on business days 9:00 — 17:00 CET.",
|
||||
|
||||
// Legal
|
||||
"legal.privacy.title": "Privacy",
|
||||
"legal.privacy.body": "Bookra processes the data needed to create bookings, manage accounts, send reminders, and secure the service. Tenant data is isolated and access is limited by user role.",
|
||||
"legal.privacy.data.title": "Data we process",
|
||||
"legal.privacy.data.body": "Customer contact details, booking times, booking notes, workspace account details, and technical records needed for secure operations.",
|
||||
"legal.privacy.rights.title": "Rights and requests",
|
||||
"legal.privacy.rights.body": "Access, correction, and deletion requests are handled by the operator of the relevant workspace. Bookra provides the technical system for secure processing.",
|
||||
"legal.terms.title": "Terms",
|
||||
"legal.terms.body": "Bookra is booking management software for local services. Workspace operators are responsible for their offer, availability, and customer communication.",
|
||||
"legal.terms.service.title": "Using the service",
|
||||
"legal.terms.service.body": "The service must be used lawfully, without abusing booking forms, bypassing security, or uploading prohibited content.",
|
||||
"legal.terms.billing.title": "Subscription",
|
||||
"legal.terms.billing.body": "Paid plans are billed through Paddle. The active plan controls limits, add-ons, and support features.",
|
||||
},
|
||||
} satisfies Record<Locale, Record<string, string>>;
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import {
|
||||
createContext,
|
||||
createEffect,
|
||||
createSignal,
|
||||
ParentComponent,
|
||||
useContext,
|
||||
} from "solid-js";
|
||||
|
||||
type Theme = "light" | "dark" | "system";
|
||||
|
||||
const STORAGE_KEY = "bookra-theme";
|
||||
|
||||
function getInitialTheme(): Theme {
|
||||
if (typeof window === "undefined") return "system";
|
||||
const stored = localStorage.getItem(STORAGE_KEY) as Theme | null;
|
||||
return stored ?? "system";
|
||||
}
|
||||
|
||||
function getResolvedTheme(theme: Theme): "light" | "dark" {
|
||||
if (theme === "system") {
|
||||
return window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
? "dark"
|
||||
: "light";
|
||||
}
|
||||
return theme;
|
||||
}
|
||||
|
||||
type ThemeContextValue = {
|
||||
theme: () => Theme;
|
||||
resolvedTheme: () => "light" | "dark";
|
||||
setTheme: (theme: Theme) => void;
|
||||
toggle: () => void;
|
||||
};
|
||||
|
||||
const ThemeContext = createContext<ThemeContextValue>();
|
||||
|
||||
export const ThemeProvider: ParentComponent = (props) => {
|
||||
const [theme, setThemeSignal] = createSignal<Theme>(getInitialTheme());
|
||||
const [resolvedTheme, setResolvedTheme] = createSignal<"light" | "dark">(
|
||||
getResolvedTheme(getInitialTheme())
|
||||
);
|
||||
|
||||
const applyTheme = (t: Theme) => {
|
||||
const resolved = getResolvedTheme(t);
|
||||
setResolvedTheme(resolved);
|
||||
|
||||
const root = document.documentElement;
|
||||
root.classList.remove("light", "dark");
|
||||
root.classList.add(resolved);
|
||||
root.setAttribute("data-theme", resolved);
|
||||
};
|
||||
|
||||
createEffect(() => {
|
||||
const t = theme();
|
||||
localStorage.setItem(STORAGE_KEY, t);
|
||||
applyTheme(t);
|
||||
});
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
mediaQuery.addEventListener("change", () => {
|
||||
if (theme() === "system") {
|
||||
applyTheme("system");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const setTheme = (t: Theme) => setThemeSignal(t);
|
||||
|
||||
const toggle = () => {
|
||||
const current = resolvedTheme();
|
||||
setTheme(current === "light" ? "dark" : "light");
|
||||
};
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{ theme, resolvedTheme, setTheme, toggle }}>
|
||||
{props.children}
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export function useTheme() {
|
||||
const context = useContext(ThemeContext);
|
||||
if (!context) {
|
||||
throw new Error("ThemeProvider is missing from the component tree.");
|
||||
}
|
||||
return context;
|
||||
}
|
||||
Reference in New Issue
Block a user