mirror of
https://github.com/Dvorinka/Bookra.git
synced 2026-10-29 07:54:44 +00:00
feat(core): consolidate auth service into backend and implement stripe billing
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:
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({
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user