feat(core): consolidate auth service into backend and implement stripe billing
CI / Frontend (push) Successful in 9m54s
CI / Go - apps/auth-service (push) Failing after 24s
CI / Go - apps/backend (push) Failing after 5m43s
CI / Docker publish - auth-service (push) Has been skipped
CI / Docker publish - backend (push) Has been skipped

This commit performs a major architectural refactor by migrating the standalone `auth-service` into the main `backend` application, enabling a unified codebase and simplified deployment. It also introduces comprehensive Stripe billing support and a new administrative dashboard.

Key changes:
- **Architecture**: Deleted `apps/auth-service` and integrated its functionality (JWT, magic links, OAuth, user management) into `apps/backend`.
- **Billing**: Added Stripe integration to `backend`, supporting both monthly and yearly subscription cycles with automatic plan entitlement enforcement (e.g., location limits).
- **Admin Dashboard**: Implemented a new administrative service and API endpoints to manage tenants, users, and view platform-wide statistics.
- **Frontend**:
    - Added a new pricing page with monthly/yearly toggle and comparison table.
    - Integrated Stripe and Sentry for payments and error tracking.
    - Improved dashboard UX/UI and added i18n support for new features.
    - Enhanced the public booking flow with better validation and contact form integration.
- **Database**: Added migrations for users, magic links, password resets, OAuth states, admin audit logs, and refresh tokens.
- **DevOps**: Updated environment configurations for Railway and Vercel, and streamlined the project's `package.json` scripts.
This commit is contained in:
Tomas Dvorak
2026-05-09 18:25:25 +02:00
parent cf3315e8fc
commit 164a37e997
69 changed files with 4630 additions and 5260 deletions
@@ -16,6 +16,8 @@ export function PublicBookingRoute() {
const [customerName, setCustomerName] = createSignal("");
const [customerEmail, setCustomerEmail] = createSignal("");
const [notes, setNotes] = createSignal("");
const [highlightContact, setHighlightContact] = createSignal(false);
let contactFormRef: HTMLDivElement | undefined;
const [availability, { refetch }] = createResource(() => {
const slug = tenantSlug();
if (!slug) return null;
@@ -31,6 +33,9 @@ export function PublicBookingRoute() {
const bookSlot = async (slot: components["schemas"]["TimeSlot"]) => {
if (!customerName().trim() || !customerEmail().trim()) {
setBookingError(i18n.t("booking.customerRequired"));
setHighlightContact(true);
contactFormRef?.scrollIntoView({ behavior: "smooth", block: "center" });
setTimeout(() => setHighlightContact(false), 2000);
return;
}
@@ -105,8 +110,8 @@ export function PublicBookingRoute() {
<Show when={tenantSlug()}>
<div class="grid gap-8 lg:grid-cols-[1.1fr_0.9fr]">
{/* Sidebar - Contact form - ORDER 1 on mobile, 2 on desktop */}
<div class="space-y-6 order-1 lg:order-2">
<Card class="surface-elevated animate-slide-up" style={{ "animation-delay": "0.3s" }}>
<div class="space-y-6 order-1 lg:order-2" ref={(el) => { contactFormRef = el; }}>
<Card class={`surface-elevated animate-slide-up transition-all duration-300 ${highlightContact() ? 'ring-2 ring-accent shadow-lg' : ''}`} style={{ "animation-delay": "0.3s" }}>
<CardHeader>
<CardTitle class="font-display text-xl">{i18n.t("booking.customer.title")}</CardTitle>
<CardDescription>{i18n.t("booking.customer.body")}</CardDescription>