feat(site): landing-only on www, app gated to demo subdomain (#20)
CI / Frontend (push) Canceled after 0s
CI / Go - apps/backend (push) Canceled after 0s
CI / Docker publish - backend (push) Canceled after 0s

* feat(site): landing-only on www host, app gated to demo subdomain

- RequireAppHost redirects app routes to / on non-demo hosts
- Signup CTAs swapped for launch-notification mailto on www
- Pricing marked as indicative pre-launch, no subscription purchasable
- SEO: OG/Twitter cards, og.png, JSON-LD, robots.txt, sitemap.xml,
  per-route document titles, SPA rewrite for Vercel

Authored By: TDvorak <[email protected]>

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* chore: ignore vercel link artifacts

Authored By: TDvorak <[email protected]>

* docs: document www/demo host split in deployment targets

Authored By: TDvorak <[email protected]>

---------

Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomáš DvořákandDevin authored and GitHub committed 2026-09-25 16:20:05 +02:00
1 parent 27a51c38b8
commit 7a31f55f81
15 files changed
+174 -68

No files matched your search

+2
View File
@@ -0,0 +1,2 @@
.vercel
.env*
+28
View File
@@ -10,12 +10,40 @@
<link rel="canonical" href="https://bookra.eu" />
<title>Bookra — Jednoduchý rezervační software</title>
<meta name="robots" content="index,follow" />
<link rel="apple-touch-icon" href="/favicon.svg" />
<!-- Open Graph -->
<meta property="og:type" content="website" />
<meta property="og:url" content="https://bookra.eu" />
<meta property="og:title" content="Bookra — Jednoduchý rezervační software" />
<meta property="og:description" content="Spravujte rezervace, zákazníky a tým na jednom místě. Bez zbytečné složitosti." />
<meta property="og:locale" content="cs_CZ" />
<meta property="og:site_name" content="Bookra" />
<meta property="og:image" content="https://bookra.eu/og.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Bookra — Jednoduchý rezervační software" />
<meta name="twitter:description" content="Spravujte rezervace, zákazníky a tým na jednom místě. Bez zbytečné složitosti." />
<meta name="twitter:image" content="https://bookra.eu/og.png" />
<!-- Structured data -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "Bookra",
"url": "https://bookra.eu",
"applicationCategory": "BusinessApplication",
"operatingSystem": "Web",
"inLanguage": ["cs", "en"],
"description": "Jednoduchý rezervační software pro salony, kliniky a lokální služby. Rezervace, zákazníci, tým a připomenutí na jednom místě.",
"offers": { "@type": "Offer", "price": "0", "priceCurrency": "CZK", "availability": "https://schema.org/PreOrder" }
}
</script>
<!-- Preconnect to Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

+8
View File
@@ -0,0 +1,8 @@
User-agent: *
Allow: /
Disallow: /dashboard
Disallow: /book/
Disallow: /manage/
Disallow: /auth/
Sitemap: https://bookra.eu/sitemap.xml
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>https://bookra.eu/</loc><priority>1.0</priority></url>
<url><loc>https://bookra.eu/pricing</loc><priority>0.9</priority></url>
<url><loc>https://bookra.eu/about</loc></url>
<url><loc>https://bookra.eu/contact</loc></url>
<url><loc>https://bookra.eu/privacy</loc></url>
<url><loc>https://bookra.eu/terms</loc></url>
</urlset>
+15 -4
View File
@@ -6,16 +6,27 @@ import { I18nProvider } from "./providers/i18n-provider";
import { ThemeProvider } from "./providers/theme-provider";
import { Shell } from "./components/shell";
const ROUTE_TITLES: Record<string, string> = {
"/pricing": "Bookra - Ceník",
"/about": "Bookra - O nás",
"/contact": "Bookra - Kontakt",
"/privacy": "Bookra - Ochrana soukromí",
"/terms": "Bookra - Obchodní podmínky",
};
// ScrollToTop component that resets scroll position on route change
const ScrollToTop = () => {
const location = useLocation();
createEffect(() => {
// Reset scroll position whenever the pathname changes
location.pathname;
// Reset scroll position and document title whenever the pathname changes
const path = location.pathname;
window.scrollTo({ top: 0, left: 0, behavior: "instant" });
document.title = ROUTE_TITLES[path] ?? (path.startsWith("/dashboard") || path.startsWith("/book") || path.startsWith("/manage")
? "Bookra"
: "Bookra - Jednoduchý rezervační software");
});
return null;
};
+6 -12
View File
@@ -3,6 +3,7 @@ import { ParentComponent, Show, createSignal, onMount, onCleanup } from "solid-j
import { useAuth } from "../providers/auth-provider";
import { useI18n } from "../providers/i18n-provider";
import { useTheme } from "../providers/theme-provider";
import { isAppHost } from "../lib/app-host";
import { BookraCharacter } from "./bookra-character";
import {
Button,
@@ -96,10 +97,7 @@ export const Shell: ParentComponent = (props) => {
];
// Demo mode detection
const isDemoMode = () => {
const hostname = window.location.hostname;
return hostname.includes("demo") || hostname === "localhost" || hostname === "127.0.0.1";
};
const isDemoMode = isAppHost;
// Listen for custom event to open auth dialog from dashboard
onMount(() => {
@@ -284,12 +282,9 @@ export const Shell: ParentComponent = (props) => {
<Show when={isDemoMode()} fallback={
<Button
size="sm"
onClick={() => {
setAuthMode("sign-in");
setSignInOpen(true);
}}
onClick={() => { window.location.href = "https://demo.bookra.eu"; }}
>
{i18n.t("auth.signIn")}
{i18n.locale() === 'cs' ? 'Vyzkoušet demo' : 'Try demo'}
</Button>
}>
<Button
@@ -379,12 +374,11 @@ export const Shell: ParentComponent = (props) => {
<Button
fullWidth
onClick={() => {
setAuthMode("sign-in");
setSignInOpen(true);
setMobileMenuOpen(false);
window.location.href = "https://demo.bookra.eu";
}}
>
{i18n.t("auth.signIn")}
{i18n.locale() === 'cs' ? 'Vyzkoušet demo' : 'Try demo'}
</Button>
}>
<Button
+10
View File
@@ -0,0 +1,10 @@
// The public www host serves the landing only until launch; the app lives on the demo subdomain.
export const isAppHost = () => {
const hostname = window.location.hostname;
return hostname.includes("demo") || hostname === "localhost" || hostname === "127.0.0.1";
};
export const appUrl = (path = "") => (isAppHost() ? path : `https://demo.bookra.eu${path}`);
export const WAITLIST_MAILTO =
"mailto:[email protected]?subject=" + encodeURIComponent("Bookra - oznamte mi spuštění");
+25 -13
View File
@@ -1,12 +1,24 @@
import { render } from "solid-js/web";
import { lazy } from "solid-js";
import { Route, Router } from "@solidjs/router";
import { lazy, onMount, type Component, type ParentProps } from "solid-js";
import { Route, Router, useNavigate } from "@solidjs/router";
import App from "./App";
import "./styles/index.css";
import { initSentry } from "./lib/sentry";
import { isAppHost } from "./lib/app-host";
initSentry();
// App-only routes bounce to the landing on the public www host; the app is served on the app/demo subdomain.
const RequireAppHost = (props: ParentProps) => {
const navigate = useNavigate();
onMount(() => {
if (!isAppHost()) navigate("/", { replace: true });
});
return isAppHost() ? props.children : null;
};
const gated = (RouteComponent: Component) => () => <RequireAppHost><RouteComponent /></RequireAppHost>;
const HomeRoute = lazy(() => import("./routes/home-route").then((module) => ({ default: module.HomeRoute })));
const PricingRoute = lazy(() => import("./routes/pricing-route"));
const AboutRoute = lazy(() => import("./routes/about-route").then((module) => ({ default: module.AboutRoute })));
@@ -31,18 +43,18 @@ render(
<Route path="/" component={HomeRoute} />
<Route path="/pricing" component={PricingRoute} />
<Route path="/about" component={AboutRoute} />
<Route path="/auth/callback" component={AuthCallbackRoute} />
<Route path="/auth/callback" component={gated(AuthCallbackRoute)} />
<Route path="/contact" component={ContactRoute} />
<Route path="/dashboard" component={DashboardRoute} />
<Route path="/dashboard/bookings" component={DashboardBookingsRoute} />
<Route path="/dashboard/customers" component={DashboardCustomersRoute} />
<Route path="/dashboard/zones" component={DashboardZonesRoute} />
<Route path="/dashboard/services" component={DashboardServicesRoute} />
<Route path="/dashboard/staff" component={DashboardStaffRoute} />
<Route path="/dashboard/billing" component={DashboardBillingRoute} />
<Route path="/dashboard/settings" component={DashboardSettingsRoute} />
<Route path="/book/:tenantSlug" component={PublicBookingRoute} />
<Route path="/manage/:reference" component={BookingManageRoute} />
<Route path="/dashboard" component={gated(DashboardRoute)} />
<Route path="/dashboard/bookings" component={gated(DashboardBookingsRoute)} />
<Route path="/dashboard/customers" component={gated(DashboardCustomersRoute)} />
<Route path="/dashboard/zones" component={gated(DashboardZonesRoute)} />
<Route path="/dashboard/services" component={gated(DashboardServicesRoute)} />
<Route path="/dashboard/staff" component={gated(DashboardStaffRoute)} />
<Route path="/dashboard/billing" component={gated(DashboardBillingRoute)} />
<Route path="/dashboard/settings" component={gated(DashboardSettingsRoute)} />
<Route path="/book/:tenantSlug" component={gated(PublicBookingRoute)} />
<Route path="/manage/:reference" component={gated(BookingManageRoute)} />
<Route path="/:kind" component={LegalRoute} matchFilters={{ kind: ["privacy", "terms"] }} />
<Route path="*404" component={NotFoundRoute} />
</Router>
+41 -30
View File
@@ -1,4 +1,4 @@
import { A, useNavigate } from "@solidjs/router";
import { useNavigate } from "@solidjs/router";
import { createSignal, onMount, createMemo, Show, For } from "solid-js";
import { useI18n } from "../providers/i18n-provider";
import { useTheme } from "../providers/theme-provider";
@@ -8,6 +8,7 @@ import { DashboardMockup } from "../components/dashboard-mockup";
import { VideoPlayer } from "../components/video-player";
import { AnimatedList } from "../components/animated-list";
import { FloatingDock } from "../components/floating-dock";
import { isAppHost, appUrl, WAITLIST_MAILTO } from "../lib/app-host";
const HomeLogo = () => {
const theme = useTheme();
@@ -228,13 +229,16 @@ export function HomeRoute() {
const navigate = useNavigate();
// Pre-launch: signup CTAs collect launch notifications on www; on the app host they enter the app.
const primaryHref = () => (isAppHost() ? "/dashboard" : WAITLIST_MAILTO);
const primaryLabel = () =>
isAppHost() ? i18n.t("home.hero.cta.primary") : isCs() ? "Oznamte mi spuštění" : "Notify me at launch";
onMount(() => {
setIsVisible(true);
// Demo mode: redirect landing page to dashboard immediately
const hostname = window.location.hostname;
const isDemo = hostname.includes("demo") || hostname === "localhost" || hostname === "127.0.0.1";
if (isDemo) {
if (isAppHost()) {
navigate("/dashboard");
}
});
@@ -379,15 +383,15 @@ export function HomeRoute() {
{i18n.t("home.hero.subtitle")}
</p>
<div class="flex flex-col sm:flex-row items-center justify-center gap-4 animate-slide-up" style={{ "animation-delay": "0.3s" }}>
<A
href="/dashboard"
<a
href={primaryHref()}
class="btn-primary inline-flex items-center gap-2 text-base px-8 py-4 group"
>
{i18n.t("home.hero.cta.primary")}
{primaryLabel()}
<span class="transition-transform duration-200 group-hover:translate-x-1">
<ArrowRightIcon />
</span>
</A>
</a>
<a
href="https://demo.bookra.eu"
target="_blank"
@@ -560,7 +564,7 @@ export function HomeRoute() {
description: isCs()
? "Sledujte KPI, trendy rezervací a dnešní agenda v reálném čase."
: "Track KPIs, booking trends, and today's agenda in real time.",
href: "/dashboard",
href: appUrl("/dashboard"),
children: <DashboardMockup />,
fadeBottom: true,
},
@@ -569,7 +573,7 @@ export function HomeRoute() {
description: isCs()
? "Intuitivní týdenní a měsíční pohled s drag-and-drop plánováním."
: "Intuitive weekly and monthly views with drag-and-drop scheduling.",
href: "/dashboard/bookings",
href: appUrl("/dashboard/bookings"),
children: (
<div class="w-full h-full flex flex-col gap-2">
<div class="flex items-center justify-between">
@@ -620,7 +624,7 @@ export function HomeRoute() {
description: isCs()
? "Historie návštěv, poznámky a preference na jednom místě."
: "Visit history, notes, and preferences all in one place.",
href: "/dashboard/customers",
href: appUrl("/dashboard/customers"),
children: (
<div class="w-full h-full flex flex-col gap-2">
<div class="flex items-center gap-2 mb-1">
@@ -654,7 +658,7 @@ export function HomeRoute() {
description: isCs()
? "Podrobné statistiky, exporty a přehledy pro váš podnik."
: "Detailed statistics, exports, and business insights.",
href: "/dashboard?tab=analytics",
href: appUrl("/dashboard"),
children: (
<div class="w-full h-full flex flex-col gap-3">
<div class="flex items-center justify-between">
@@ -709,13 +713,13 @@ export function HomeRoute() {
{i18n.t("home.how.subtitle")}
</p>
<div class="flex items-center gap-4 mb-8">
<A
href="/dashboard"
<a
href={primaryHref()}
class="btn-primary inline-flex items-center gap-2"
>
{i18n.t("home.how.cta")}
{primaryLabel()}
<ArrowRightIcon />
</A>
</a>
{/* Character guide */}
<div class="hidden sm:block">
<BookraCharacter pose="walk" size="sm" animate={true} />
@@ -801,6 +805,13 @@ export function HomeRoute() {
<p class="text-lg text-ink-muted animate-slide-up" style={{ "animation-delay": "0.2s" }}>
{i18n.t("home.pricing.subtitle")}
</p>
<Show when={!isAppHost()}>
<p class="text-sm text-ink-subtle mt-4 animate-slide-up" style={{ "animation-delay": "0.25s" }}>
{isCs()
? "Bookra se spouští brzy. Předplatné zatím nelze zakoupit, ceny jsou orientační."
: "Bookra launches soon. Subscriptions are not yet available; prices are indicative."}
</p>
</Show>
</div>
{/* Billing Toggle */}
@@ -899,12 +910,12 @@ export function HomeRoute() {
</li>
))}
</ul>
<A
href="/dashboard"
<a
href={isAppHost() ? "/dashboard" : WAITLIST_MAILTO}
class={`block text-center py-3 px-6 rounded-button font-display font-medium transition-all duration-200 w-full ${plan.popular ? 'bg-canvas text-ink hover:bg-canvas-subtle shadow-lg group-hover:shadow-xl' : 'btn-secondary'}`}
>
{plan.cta}
</A>
{isAppHost() || plan.id === "business" ? plan.cta : primaryLabel()}
</a>
</div>
</div>
))}
@@ -984,15 +995,15 @@ export function HomeRoute() {
{i18n.t("home.cta.subtitle")}
</p>
<div class="flex flex-col sm:flex-row items-center justify-center gap-4 animate-slide-up" style={{ "animation-delay": "0.2s" }}>
<A
href="/dashboard"
<a
href={primaryHref()}
class="btn-primary inline-flex items-center gap-2 text-base px-8 py-4 group shadow-lg hover:shadow-xl transition-all duration-300"
>
{i18n.t("home.cta.primary")}
{primaryLabel()}
<span class="transition-transform duration-200 group-hover:translate-x-1">
<ArrowRightIcon />
</span>
</A>
</a>
<a
href="https://demo.bookra.eu"
target="_blank"
@@ -1053,10 +1064,10 @@ export function HomeRoute() {
: "New bookings, reminders, and alerts — everything at a glance."}
</p>
<div class="flex items-center gap-4 animate-slide-up" style={{ "animation-delay": "0.3s" }}>
<A href="/dashboard" class="btn-primary inline-flex items-center gap-2">
{i18n.t("home.cta.primary")}
<a href={primaryHref()} class="btn-primary inline-flex items-center gap-2">
{primaryLabel()}
<ArrowRightIcon />
</A>
</a>
</div>
</div>
<div class="animate-slide-up" style={{ "animation-delay": "0.3s" }}>
@@ -1213,10 +1224,10 @@ export function HomeRoute() {
: "Manage bookings, customers, and your team from anywhere. An intuitive interface designed for mobile."}
</p>
<div class="flex items-center gap-4 animate-slide-up" style={{ "animation-delay": "0.3s" }}>
<A href="/dashboard" class="btn-primary inline-flex items-center gap-2">
{i18n.t("home.cta.primary")}
<a href={primaryHref()} class="btn-primary inline-flex items-center gap-2">
{primaryLabel()}
<ArrowRightIcon />
</A>
</a>
</div>
</div>
</div>
+20 -7
View File
@@ -1,6 +1,7 @@
import { createSignal, createMemo, Show, For } from "solid-js";
import { useNavigate } from "@solidjs/router";
import { useI18n } from "../providers/i18n-provider";
import { isAppHost, WAITLIST_MAILTO } from "../lib/app-host";
const PricingRoute = () => {
const navigate = useNavigate();
@@ -81,9 +82,14 @@ const PricingRoute = () => {
]);
const handleSelectPlan = (planId: string) => {
// Redirect to signup with plan selection
// Pre-launch: www collects launch notifications; the app host keeps the signup flow.
if (!isAppHost()) {
window.location.href = WAITLIST_MAILTO;
return;
}
navigate("/?signup=true&plan=" + planId + "&billing=" + billingInterval());
};
const waitlistLabel = () => (isCs() ? "Oznamte mi spuštění" : "Notify me at launch");
const { t } = useI18n();
@@ -179,6 +185,13 @@ const PricingRoute = () => {
? "Vyberte si plán, který vyhovuje vašemu podnikání. Žádné skryté poplatky, žádné překvapení."
: "Choose a plan that fits your business. No hidden fees, no surprises."}
</p>
<Show when={!isAppHost()}>
<p class="text-sm text-ink-subtle mb-8 animate-slide-up" style={{ "animation-delay": "0.15s" }}>
{isCs()
? "Bookra se spouští brzy. Předplatné zatím nelze zakoupit, ceny jsou orientační."
: "Bookra launches soon. Subscriptions are not yet available; prices are indicative."}
</p>
</Show>
{/* Billing Toggle */}
<div class="flex items-center justify-center mb-8 animate-slide-up" style={{ "animation-delay": "0.2s" }}>
@@ -285,7 +298,7 @@ const PricingRoute = () => {
: 'bg-ink text-canvas hover:bg-ink/90 hover:shadow-lg hover:-translate-y-0.5'
}`}
>
{plan.cta}
{isAppHost() || plan.id === "business" ? plan.cta : waitlistLabel()}
</button>
</div>
</div>
@@ -388,15 +401,15 @@ const PricingRoute = () => {
{isCs() ? "Stále si nejste jistí?" : "Still not sure?"}
</h2>
<p class="text-ink-muted mb-6">
{isCs()
? "Začněte s bezplatným 15denním trial a rozhodněte se později."
: "Start with a free 15-day trial and decide later."}
{isCs()
? "Bookra se spouští brzy. Nechte nám e-mail a po spuštění dostanete 15denní trial zdarma."
: "Bookra launches soon. Leave us your email and get a free 15-day trial at launch."}
</p>
<a
href="/dashboard?signup=true"
href={isAppHost() ? "/dashboard?signup=true" : WAITLIST_MAILTO}
class="inline-block px-8 py-3 bg-accent text-white font-semibold rounded-xl hover:bg-accent/90 hover:shadow-lg hover:-translate-y-0.5 transition-all duration-300"
>
{isCs() ? "Začít zdarma" : "Start for free"}
{isAppHost() ? (isCs() ? "Začít zdarma" : "Start for free") : waitlistLabel()}
</a>
</div>
</section>
+1 -1
View File
@@ -1 +1 @@
{"root":["./src/App.tsx","./src/main.tsx","./src/components/animated-list.tsx","./src/components/bookra-character.tsx","./src/components/calendar-feed.tsx","./src/components/dashboard-mockup.tsx","./src/components/floating-dock.tsx","./src/components/hover-feature-cards.tsx","./src/components/index.ts","./src/components/integration-modal.tsx","./src/components/location-map.tsx","./src/components/pinned-list.tsx","./src/components/shell.tsx","./src/components/sms-settings.tsx","./src/components/video-player.tsx","./src/components/widget-builder.tsx","./src/components/dashboard/activity-timeline.tsx","./src/components/dashboard/calendar-view.tsx","./src/components/dashboard/export-csv-button.tsx","./src/components/dashboard/icons.tsx","./src/components/dashboard/kpi-card.tsx","./src/components/dashboard/notification-dropdown.tsx","./src/components/dashboard/revenue-chart.tsx","./src/components/dashboard/types.ts","./src/components/ui/avatar.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/dialog.tsx","./src/components/ui/index.ts","./src/components/ui/input.tsx","./src/components/ui/select.tsx","./src/components/ui/skeleton.tsx","./src/components/ui/tabs.tsx","./src/components/ui/textarea.tsx","./src/components/ui/tooltip.tsx","./src/lib/api-client.ts","./src/lib/map.ts","./src/lib/sentry.ts","./src/lib/types.ts","./src/providers/auth-provider.tsx","./src/providers/i18n-provider.tsx","./src/providers/theme-provider.tsx","./src/routes/about-route.tsx","./src/routes/auth-callback-route.tsx","./src/routes/booking-manage-route.tsx","./src/routes/contact-route.tsx","./src/routes/home-route.tsx","./src/routes/legal-route.tsx","./src/routes/not-found-route.tsx","./src/routes/pricing-route.tsx","./src/routes/public-booking-route.tsx","./src/routes/dashboard/billing-page.tsx","./src/routes/dashboard/bookings-page.tsx","./src/routes/dashboard/customers-page.tsx","./src/routes/dashboard/dashboard-data.ts","./src/routes/dashboard/layout.tsx","./src/routes/dashboard/overview-page.tsx","./src/routes/dashboard/services-page.tsx","./src/routes/dashboard/settings-page.tsx","./src/routes/dashboard/staff-page.tsx","./src/routes/dashboard/zones-page.tsx","./vite.config.ts"],"version":"5.9.3"}
{"root":["./src/App.tsx","./src/main.tsx","./src/components/animated-list.tsx","./src/components/bookra-character.tsx","./src/components/calendar-feed.tsx","./src/components/dashboard-mockup.tsx","./src/components/floating-dock.tsx","./src/components/hover-feature-cards.tsx","./src/components/index.ts","./src/components/integration-modal.tsx","./src/components/location-map.tsx","./src/components/pinned-list.tsx","./src/components/shell.tsx","./src/components/sms-settings.tsx","./src/components/video-player.tsx","./src/components/widget-builder.tsx","./src/components/dashboard/activity-timeline.tsx","./src/components/dashboard/calendar-view.tsx","./src/components/dashboard/export-csv-button.tsx","./src/components/dashboard/icons.tsx","./src/components/dashboard/kpi-card.tsx","./src/components/dashboard/notification-dropdown.tsx","./src/components/dashboard/revenue-chart.tsx","./src/components/dashboard/types.ts","./src/components/ui/avatar.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/dialog.tsx","./src/components/ui/index.ts","./src/components/ui/input.tsx","./src/components/ui/select.tsx","./src/components/ui/skeleton.tsx","./src/components/ui/tabs.tsx","./src/components/ui/textarea.tsx","./src/components/ui/tooltip.tsx","./src/lib/api-client.ts","./src/lib/app-host.ts","./src/lib/map.ts","./src/lib/sentry.ts","./src/lib/types.ts","./src/providers/auth-provider.tsx","./src/providers/i18n-provider.tsx","./src/providers/theme-provider.tsx","./src/routes/about-route.tsx","./src/routes/auth-callback-route.tsx","./src/routes/booking-manage-route.tsx","./src/routes/contact-route.tsx","./src/routes/home-route.tsx","./src/routes/legal-route.tsx","./src/routes/not-found-route.tsx","./src/routes/pricing-route.tsx","./src/routes/public-booking-route.tsx","./src/routes/dashboard/billing-page.tsx","./src/routes/dashboard/bookings-page.tsx","./src/routes/dashboard/customers-page.tsx","./src/routes/dashboard/dashboard-data.ts","./src/routes/dashboard/layout.tsx","./src/routes/dashboard/overview-page.tsx","./src/routes/dashboard/services-page.tsx","./src/routes/dashboard/settings-page.tsx","./src/routes/dashboard/staff-page.tsx","./src/routes/dashboard/zones-page.tsx","./vite.config.ts"],"version":"5.9.3"}
+3
View File
@@ -0,0 +1,3 @@
{
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
}