refactor(db): replace sqlc with handwritten pgx repos

- Finish Paddle-to-Stripe billing migration; drop paddle/stripe libs
- Renumber migrations; add 00012 memberships+manage_token,
  00013 location_type_capacity
- Add tests: admin, auth, bookings customer service
- Remove graphify-out artifacts and stale env files

Authored By: TDvorak <[email protected]>

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomas DvorakandDevin committed 2026-09-17 20:21:34 +02:00
1 parent 2039669e2c
commit f2f7439050
93 files changed
+6987 -69060

No files matched your search

@@ -1,4 +1,4 @@
import { Show, createSignal } from "solid-js";
import { Show, createEffect, createResource, createSignal } from "solid-js";
import { apiClient } from "../../lib/api-client";
import { Input } from "../../components/ui/input";
import { Textarea } from "../../components/ui/textarea";
@@ -16,6 +16,26 @@ function SettingsPage() {
const [emailBody, setEmailBody] = createSignal("");
const [emailSaving, setEmailSaving] = createSignal(false);
const [emailTemplates, { refetch: refetchTemplates }] = createResource(
() => data.token(),
async (bearer) => {
if (!bearer || bearer.startsWith("demo.")) return [];
try {
const response = await (apiClient as any).GET("/v1/tenants/email-templates", { headers: { Authorization: `Bearer ${bearer}` } });
return (response.data?.templates as any[]) ?? [];
} catch {
return [];
}
},
);
// Prefill the editor from the stored template whenever the type changes.
createEffect(() => {
const tpl = (emailTemplates() ?? []).find((t: any) => t.type === activeEmailType());
setEmailSubject(tpl?.subject ?? "");
setEmailBody(tpl?.bodyText ?? tpl?.bodyHtml ?? "");
});
const presetColors = [
{ name: data.i18n.locale() === "cs" ? "Hnědá" : "Terracotta", color: "#c25e3a" },
{ name: data.i18n.locale() === "cs" ? "Zelená" : "Sage", color: "#5a7c5a" },
@@ -68,7 +88,7 @@ function SettingsPage() {
<div class="w-10 h-10 rounded-full flex items-center justify-center text-canvas font-bold" style={{ background: brandColor() }}>B</div>
<div>
<p class="font-semibold text-ink">Bookra</p>
<p class="text-xs text-ink-muted">{data.i18n.locale() === "cs" ? "Rezervujte si termin" : "Book an appointment"}</p>
<p class="text-xs text-ink-muted">{data.i18n.locale() === "cs" ? "Rezervujte si termín" : "Book an appointment"}</p>
</div>
</div>
<button class="w-full py-2.5 rounded-lg text-canvas font-medium text-sm" style={{ background: brandColor() }}>{data.i18n.locale() === "cs" ? "Rezervovat" : "Book Now"}</button>
@@ -109,7 +129,8 @@ function SettingsPage() {
if (!bearer || bearer.startsWith("demo.")) { data.setDemoNotice(data.i18n.locale() === "cs" ? "V demo režimu nelze ukládat." : "Cannot save in demo mode."); return; }
setEmailSaving(true);
try {
await (apiClient as any).PUT("/v1/tenants/email-template", { headers: { Authorization: `Bearer ${bearer}` }, body: { type: activeEmailType(), subject: emailSubject(), body: emailBody() } });
await (apiClient as any).PUT("/v1/tenants/email-template", { headers: { Authorization: `Bearer ${bearer}` }, body: { type: activeEmailType(), subject: emailSubject(), body: emailBody(), isEnabled: true } });
void refetchTemplates();
data.setDemoNotice(data.i18n.locale() === "cs" ? "Šablona uložena." : "Template saved.");
} catch { data.setDemoNotice(data.i18n.locale() === "cs" ? "Chyba při ukládání." : "Save failed."); }
finally { setEmailSaving(false); }