Files
Stefan Lazic 68a62e02a4
CI / ci (push) Canceled after 0s
Remaining features
Built
No-show flag and block:
Two new numbers under Booking Rules (blank means off).
A symbol appears on the clients list, client page, calendar entries and appointment pages.
Online booking is refused at the block number, and customers are told at the details step.
Staff can still book a blocked customer in, and an owner can reset someone with a two-tap button.
The count resets when they attend, and matches the same email or phone, so a guest can’t dodge it with a new profile.
Email rules: staff and owners get email only for customer cancellations, schedule changes and invitations. Routine events (new booking, reschedule) are push-only. Nobody is told about their own action. A barber reassignment sends one customer email instead of two. Reward expiry sends one warning.
Email redesign: your brand colour, a details card, Add to calendar and View on map links, a preferences link, hidden preview text, and dark mode. Replies now go to the business’s own email.
WhatsApp “due now” queue: a list in time order with a highlighted next one, an owner setting for how many hours ahead (12–72), and a once-a-day push telling each barber how many reminders are waiting.
Monitoring: /api/health now reports whether notifications are actually going out. The worker resets stuck deliveries and alerts the owner on the first setup failure (such as a bad SMTP login). The app emails your operator address about server errors, and the manager pushes that address to each business.
Google scope: now only calendar.app.created. Events go to a calendar the app creates in the staff member’s Google account, called “<Business> bookings”.
2026-10-17 23:10:18 +01:00

58 lines
2.4 KiB
Docker

# Standalone image for the management UI — separate from the main app's
# Dockerfile (see ../Dockerfile) since this needs the Docker CLI itself
# baked in (it shells out to `docker compose`/`docker exec` against the
# host's dockerd via the mounted socket — see src/lib/docker.ts), which
# the main business app never needs.
#
# Only one instance of this ever runs (unlike the business image, which
# is built once and reused across every business — see
# src/lib/compose-template.ts's own note) so a per-deploy `build:` in
# stacks/manager/compose.yaml, rather than a separately tagged image, is
# the simpler choice here.
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm install
FROM node:22-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
# The Docker CLI + compose plugin — this container never runs its own
# Docker daemon, it only talks to the HOST's via the socket mounted in
# stacks/infra/compose.yaml's manager service (docker.sock).
RUN apk add --no-cache docker-cli docker-cli-compose
# 7-Zip for backups (`7zz a`, plain 7z — see src/lib/sevenzip.ts).
# Alpine's package is named `7zip` and installs /usr/bin/7zz (checked
# against node:22-alpine, Alpine 3.24: `apk add 7zip` → 7-Zip 26.01).
RUN apk add --no-cache 7zip
RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 manager
COPY --from=builder --chown=manager:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=manager:nodejs /app/.next ./.next
COPY --from=builder --chown=manager:nodejs /app/public ./public
COPY --from=builder --chown=manager:nodejs /app/package.json ./package.json
COPY --from=builder --chown=manager:nodejs /app/next.config.ts ./next.config.ts
# Deliberately still root at runtime, not the `manager` user created
# above — this container's whole job is calling the Docker CLI against
# the host's socket, and on most VPS setups only root (or a user in the
# `docker` group, which would need to be created with a matching host
# GID) can use that socket. Worth being explicit about: root inside this
# container plus the mounted socket effectively means root on the host —
# this app must stay behind its own login (see src/lib/auth.ts) and never
# be exposed without that gate.
EXPOSE 3000
ENV PORT=3000
ENTRYPOINT ["node_modules/.bin/next", "start", "-p", "3000"]