feat(sms): implement SMS messaging and metered billing
CI / Frontend (push) Successful in 9m50s
CI / Go - apps/auth-service (push) Failing after 4s
CI / Go - apps/backend (push) Successful in 10m18s
CI / Docker publish - auth-service (push) Has been skipped
CI / Docker publish - backend (push) Has been skipped

Implement a complete SMS messaging system including:
- Integration with SMS Manager.cz API for sending messages.
- Metered billing via Stripe using monthly aggregate invoice items.
- Backend services for managing SMS settings, usage logging, and monthly reporting.
- Database migrations for tenant settings, usage logs, and monthly reports.
- Frontend dashboard components for SMS configuration, usage tracking, and history.
- Support for customer phone numbers in the booking flow.

Includes new migrations, backend services, and frontend UI components.
This commit is contained in:
Tomas Dvorak
2026-05-10 11:40:53 +02:00
parent 164a37e997
commit 7d3e3448cf
28 changed files with 3633 additions and 3190 deletions
@@ -15,6 +15,7 @@ export function PublicBookingRoute() {
const [submittingSlot, setSubmittingSlot] = createSignal<string | null>(null);
const [customerName, setCustomerName] = createSignal("");
const [customerEmail, setCustomerEmail] = createSignal("");
const [customerPhone, setCustomerPhone] = createSignal("");
const [notes, setNotes] = createSignal("");
const [highlightContact, setHighlightContact] = createSignal(false);
let contactFormRef: HTMLDivElement | undefined;
@@ -59,6 +60,7 @@ export function PublicBookingRoute() {
locationId: slot.locationId ?? undefined,
customerName: customerName().trim(),
customerEmail: customerEmail().trim(),
customerPhone: customerPhone().trim() || undefined,
notes: notes().trim(),
startsAt: slot.startsAt,
endsAt: slot.endsAt,
@@ -132,6 +134,13 @@ export function PublicBookingRoute() {
type="email"
value={customerEmail()}
/>
<Input
autocomplete="tel"
label={i18n.t("booking.customer.phone")}
onInput={(event) => setCustomerPhone(event.currentTarget.value)}
type="tel"
value={customerPhone()}
/>
<Textarea
label={i18n.t("booking.customer.notes")}
onInput={(event) => setNotes(event.currentTarget.value)}