diff --git a/apps/booking/.dockerignore b/apps/booking/.dockerignore new file mode 100644 index 0000000..7730896 --- /dev/null +++ b/apps/booking/.dockerignore @@ -0,0 +1,6 @@ +node_modules +.nuxt +.output +.git +dist +*.log diff --git a/apps/booking/.gitignore b/apps/booking/.gitignore new file mode 100644 index 0000000..aea644d --- /dev/null +++ b/apps/booking/.gitignore @@ -0,0 +1,7 @@ +# This app uses pnpm (pnpm-lock.yaml). Ignore stray npm lockfiles. +package-lock.json +node_modules +.nuxt +.output +.data +*.log diff --git a/apps/booking/Dockerfile b/apps/booking/Dockerfile new file mode 100644 index 0000000..ac3979e --- /dev/null +++ b/apps/booking/Dockerfile @@ -0,0 +1,22 @@ +# Production image for the dezky booking app (Nuxt 4 SSR). +# Build context = this directory (apps/booking). +# syntax=docker/dockerfile:1 + +FROM node:22-alpine AS build +WORKDIR /app +RUN corepack enable +COPY package.json pnpm-lock.yaml ./ +RUN pnpm install --frozen-lockfile +COPY . . +RUN pnpm build + +FROM node:22-alpine AS runtime +WORKDIR /app +ENV NODE_ENV=production +ENV HOST=0.0.0.0 +ENV PORT=3000 +ENV NUXT_PUBLIC_SITE_URL=https://booking.dezky.eu +# Set PLATFORM_API_INTERNAL_URL at deploy time to reach platform-api. +COPY --from=build /app/.output ./.output +EXPOSE 3000 +CMD ["node", ".output/server/index.mjs"] diff --git a/apps/booking/app.vue b/apps/booking/app.vue new file mode 100644 index 0000000..8f62b8b --- /dev/null +++ b/apps/booking/app.vue @@ -0,0 +1,3 @@ + diff --git a/apps/booking/assets/styles/base.css b/apps/booking/assets/styles/base.css new file mode 100644 index 0000000..5fb795a --- /dev/null +++ b/apps/booking/assets/styles/base.css @@ -0,0 +1,48 @@ +/* Booking app base styles + design tokens. The accent is whitelabel: each + booking page sets --accent from the tenant's brandColor at runtime (see the + booking page's :style binding); this file only provides the fallback. */ +:root { + --accent: #1a1a1a; + --accent-contrast: #ffffff; + --bg: #f6f6f7; + --surface: #ffffff; + --text: #1a1a1a; + --text-mute: #6b6b70; + --border: #e7e7ea; + --radius: 14px; + --shadow: 0 1px 2px rgba(0, 0, 0, 0.04), 0 8px 24px rgba(0, 0, 0, 0.06); + --font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; +} + +* { box-sizing: border-box; } +html, body { margin: 0; padding: 0; } +body { + background: var(--bg); + color: var(--text); + font-family: var(--font); + -webkit-font-smoothing: antialiased; + line-height: 1.5; +} +a { color: inherit; } +button { font-family: inherit; } + +/* Shared primitives used across pages */ +.bk-btn { + display: inline-flex; align-items: center; justify-content: center; gap: 8px; + height: 44px; padding: 0 20px; border-radius: 10px; border: 1px solid transparent; + font-size: 15px; font-weight: 600; cursor: pointer; transition: filter .15s, opacity .15s; +} +.bk-btn--primary { background: var(--accent); color: var(--accent-contrast); } +.bk-btn--primary:hover { filter: brightness(1.06); } +.bk-btn--ghost { background: transparent; border-color: var(--border); color: var(--text); } +.bk-btn--ghost:hover { background: #00000008; } +.bk-btn:disabled { opacity: .5; cursor: not-allowed; } + +.bk-field { display: flex; flex-direction: column; gap: 6px; } +.bk-label { font-size: 12px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--text-mute); } +.bk-input { + height: 44px; padding: 0 14px; border: 1px solid var(--border); border-radius: 10px; + background: var(--surface); font-size: 15px; color: var(--text); width: 100%; +} +.bk-input:focus { outline: 2px solid var(--accent); outline-offset: 0; border-color: transparent; } +textarea.bk-input { height: auto; padding: 12px 14px; resize: vertical; min-height: 84px; } diff --git a/apps/booking/nuxt.config.ts b/apps/booking/nuxt.config.ts new file mode 100644 index 0000000..1519add --- /dev/null +++ b/apps/booking/nuxt.config.ts @@ -0,0 +1,41 @@ +// Nuxt 4 config for the dezky public booking app (booking.dezky.eu). +// +// Fully public — NO OIDC, no sessions. It only ever calls the unauthenticated +// /api/v1/public/* endpoints on platform-api, proxied through this app's own +// nitro server routes so the internal API hostname never reaches the browser. +// Whitelabel: every page renders the tenant's branding (name + brandColor), +// fetched per request — there is no dezky-fixed accent. + +export default defineNuxtConfig({ + compatibilityDate: '2026-01-01', + devtools: { enabled: true }, + + css: ['~/assets/styles/base.css'], + + runtimeConfig: { + // Server-only: how nitro reaches platform-api inside the docker network. + platformApiUrl: process.env.PLATFORM_API_INTERNAL_URL || 'http://platform-api:3001', + public: { + siteUrl: process.env.NUXT_PUBLIC_SITE_URL + || (process.env.NODE_ENV === 'production' ? 'https://booking.dezky.eu' : 'http://localhost:3000'), + }, + }, + + app: { + head: { + htmlAttrs: { lang: 'en' }, + link: [{ rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg' }], + meta: [ + { name: 'viewport', content: 'width=device-width, initial-scale=1' }, + { name: 'robots', content: 'noindex' }, // booking pages aren't for search indexing + ], + }, + }, + + vite: { + server: { + allowedHosts: ['booking.dezky.local'], + hmr: process.env.DEZKY_TRAEFIK === '1' ? { protocol: 'wss', clientPort: 443 } : undefined, + }, + }, +}) diff --git a/apps/booking/package.json b/apps/booking/package.json new file mode 100644 index 0000000..1c559c5 --- /dev/null +++ b/apps/booking/package.json @@ -0,0 +1,27 @@ +{ + "name": "@dezky/booking", + "version": "0.0.1", + "private": true, + "description": "dezky Scheduling — public booking app (booking.dezky.eu). Unauthenticated, whitelabel per tenant.", + "scripts": { + "dev": "TMPDIR=/tmp nuxt dev --host 0.0.0.0 --port 3000", + "build": "nuxt build", + "start": "node .output/server/index.mjs", + "generate": "nuxt generate", + "preview": "nuxt preview", + "typecheck": "nuxt typecheck" + }, + "dependencies": { + "luxon": "^3.5.0", + "nuxt": "^4.4.7", + "vue": "^3.5.0", + "vue-router": "^4.4.0" + }, + "devDependencies": { + "@types/luxon": "^3.4.2", + "@types/node": "^20.0.0", + "typescript": "^5.6.0", + "vue-tsc": "^3.2.6" + }, + "packageManager": "pnpm@9.12.0" +} diff --git a/apps/booking/pages/[tenantSlug]/[hostSlug]/[eventTypeSlug].vue b/apps/booking/pages/[tenantSlug]/[hostSlug]/[eventTypeSlug].vue new file mode 100644 index 0000000..aafd9c5 --- /dev/null +++ b/apps/booking/pages/[tenantSlug]/[hostSlug]/[eventTypeSlug].vue @@ -0,0 +1,285 @@ + + +