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 committed 2026-05-09 18:25:25 +02:00
1 parent cf3315e8fc
commit 164a37e997
69 files changed
+4630 -5260

No files matched your search

@@ -19,13 +19,20 @@ export function IntegrationModal(props: IntegrationModalProps) {
setTimeout(() => setCopiedSnippet(null), 2000);
};
const hostedPageUrl = `https://bookra.eu/book/${props.tenantSlug}`;
const hostedPageUrl = props.publicBookingUrl;
const baseUrl = (() => {
try {
return new URL(props.publicBookingUrl).origin;
} catch {
return "https://bookra.eu";
}
})();
const htmlWidgetCode = `<div id="bookra-widget"></div>
<script>
(function() {
var script = document.createElement('script');
script.src = "https://bookra.eu/widget.js";
script.src = "${baseUrl}/widget.js";
script.async = true;
script.onload = function() {
BookraWidget.init({
@@ -65,7 +72,7 @@ function App() {
add_action('wp_footer', function() {
?>
<div id="bookra-widget"></div>
<script src="https://bookra.eu/widget.js" async></script>
<script src="${baseUrl}/widget.js" async></script>
<script>
window.addEventListener('load', function() {
BookraWidget.init({
+4
View File
@@ -76,6 +76,7 @@ export const Shell: ParentComponent = (props) => {
const navigate = useNavigate();
const [mobileMenuOpen, setMobileMenuOpen] = createSignal(false);
const hideHeader = () => location.pathname.startsWith("/dashboard");
const hideFooter = () => location.pathname.startsWith("/dashboard");
const [signInOpen, setSignInOpen] = createSignal(false);
const [authMode, setAuthMode] = createSignal<"sign-in" | "register">("sign-in");
const [name, setName] = createSignal("");
@@ -88,6 +89,7 @@ export const Shell: ParentComponent = (props) => {
const showGoogleSignIn = () => auth.supportsGoogleSignIn() && authMode() === "sign-in";
const navLinks = [
{ href: "/pricing", label: i18n.t("nav.pricing") },
{ href: "/about", label: i18n.t("nav.about") },
{ href: "/contact", label: i18n.t("nav.contact") },
];
@@ -409,6 +411,7 @@ export const Shell: ParentComponent = (props) => {
<main class="flex-1">{props.children}</main>
<Show when={!hideFooter()}>
{/* Footer */}
<footer class="border-t border-border/60 py-16 bg-canvas-subtle/40">
<div class="section-container">
@@ -480,6 +483,7 @@ export const Shell: ParentComponent = (props) => {
</div>
</div>
</footer>
</Show>
<Dialog open={signInOpen()} onClose={() => setSignInOpen(false)}>
<DialogHeader>
@@ -215,6 +215,7 @@ export function WidgetBuilder(props: WidgetBuilderProps) {
const [selectedSize, setSelectedSize] = createSignal<WidgetSize>("default");
const [selectedPosition, setSelectedPosition] = createSignal<WidgetPosition>("bottom-right");
const [copiedSnippet, setCopiedSnippet] = createSignal<string | null>(null);
const [copyError, setCopyError] = createSignal<string | null>(null);
const [showPreview, setShowPreview] = createSignal(true);
const [draggedIndex, setDraggedIndex] = createSignal<number | null>(null);
const [customColor, setCustomColor] = createSignal(props.config.primaryColor || "#a65c3e");
@@ -1077,9 +1078,11 @@ export class BookraWidgetComponent implements OnInit {
try {
await navigator.clipboard.writeText(text);
setCopiedSnippet(snippetId);
setCopyError(null);
setTimeout(() => setCopiedSnippet(null), 2000);
} catch (err) {
console.error("Failed to copy:", err);
setCopyError(i18n.locale() === 'cs' ? 'Kopírování se nezdařilo. Zkuste to znovu.' : 'Copy failed. Please try again.');
setTimeout(() => setCopyError(null), 3000);
}
};
@@ -1445,6 +1448,11 @@ export class BookraWidgetComponent implements OnInit {
</div>
</CardHeader>
<CardContent>
<Show when={copyError()}>
<div class="mb-4 p-3 rounded-xl bg-[hsl(var(--error-soft))] border border-[hsl(var(--error))/20] text-[hsl(var(--error))] text-sm font-medium animate-fade-in">
{copyError()}
</div>
</Show>
<Tabs defaultValue="html">
<TabsList class="mb-4 flex-wrap">
<TabsTrigger value="html">HTML</TabsTrigger>