No description
  • TypeScript 98.1%
  • CSS 1.2%
  • PLpgSQL 0.3%
  • Dockerfile 0.3%
Find a file
2026-07-31 21:57:09 +02:00
.claude securite(hygiene): garde-fous secrets & credentials (audit #3-#9, #11-#13) 2026-07-29 01:00:43 +02:00
app divers: www->apex 301 (nginx), debordement textes utilisateur, next 16.2.12, notes de deploiement (audit #51, #100, #101) 2026-07-29 01:47:11 +02:00
components divers: www->apex 301 (nginx), debordement textes utilisateur, next 16.2.12, notes de deploiement (audit #51, #100, #101) 2026-07-29 01:47:11 +02:00
deploy docs(deploy): archives nommees maj-<date>.tgz (lon* reste bloque par le garde-fou anti-secrets) 2026-07-30 07:52:53 +02:00
lib fix(build): APP_URL vide ne casse plus le prerender + build args sur migrate 2026-07-30 07:51:54 +02:00
LOGO feat(brand): integrate the real "Light on Nails" logo + favicon 2026-07-07 15:08:12 +02:00
prisma securite(hygiene): garde-fous secrets & credentials (audit #3-#9, #11-#13) 2026-07-29 01:00:43 +02:00
public/brand perf(seo): passe Lighthouse — métadonnées, canonicals, JSON-LD, images optimisées, inlineCss 2026-07-20 16:42:03 +02:00
var/uploads feat: complete LightOnNails site — booking, payments, wallet, VIP, gallery, news, admin 2026-07-07 05:18:33 +02:00
.aiexclude securite(hygiene): garde-fous secrets & credentials (audit #3-#9, #11-#13) 2026-07-29 01:00:43 +02:00
.cursorignore securite(hygiene): garde-fous secrets & credentials (audit #3-#9, #11-#13) 2026-07-29 01:00:43 +02:00
.dockerignore chore(deploy): les archives vivent dans ./archive/ (exclu de git, de l'image et des archives elles-memes) 2026-07-30 07:31:15 +02:00
.env.example securite(hygiene): garde-fous secrets & credentials (audit #3-#9, #11-#13) 2026-07-29 01:00:43 +02:00
.gitignore initial 2026-07-31 21:57:09 +02:00
AGENTS.md securite(hygiene): garde-fous secrets & credentials (audit #3-#9, #11-#13) 2026-07-29 01:00:43 +02:00
Caddyfile ship: turnkey Docker deploy with auto-HTTPS for lightonnails.online 2026-07-08 09:35:53 +02:00
CLAUDE.md Initial commit from Create Next App 2026-06-04 22:20:37 +02:00
DEPLOY.md docs(deploy): archives nommees maj-<date>.tgz (lon* reste bloque par le garde-fou anti-secrets) 2026-07-30 07:52:53 +02:00
DESIGN.md redesign: clean, elegant design drawn from the brand logo 2026-07-07 15:44:42 +02:00
docker-compose.override.yml deploy: support an existing nginx reverse proxy (disable bundled Caddy) 2026-07-12 13:02:38 +02:00
docker-compose.yml fix(build): APP_URL vide ne casse plus le prerender + build args sur migrate 2026-07-30 07:51:54 +02:00
Dockerfile seo(robots): sitemap annonce la vraie origine + APP_URL fourni au build Docker (audit #46 Critique, #48, #95) 2026-07-29 00:56:27 +02:00
eslint.config.mjs Initial commit from Create Next App 2026-06-04 22:20:37 +02:00
next.config.ts perf(guide1): LCP, CLS et budget des tiers (audit #31-#43, #52) 2026-07-29 01:28:44 +02:00
package-lock.json divers: www->apex 301 (nginx), debordement textes utilisateur, next 16.2.12, notes de deploiement (audit #51, #100, #101) 2026-07-29 01:47:11 +02:00
package.json divers: www->apex 301 (nginx), debordement textes utilisateur, next 16.2.12, notes de deploiement (audit #51, #100, #101) 2026-07-29 01:47:11 +02:00
postcss.config.mjs Initial commit from Create Next App 2026-06-04 22:20:37 +02:00
prisma.config.ts feat: complete LightOnNails site — booking, payments, wallet, VIP, gallery, news, admin 2026-07-07 05:18:33 +02:00
PRODUCT.md feat(design): "Riviera éditorial" summer redesign + self-healing brand assets 2026-07-07 12:33:03 +02:00
proxy.ts harden: reject unsafe HTTP methods + favicon redirect (Next 16 proxy) 2026-07-07 22:11:53 +02:00
README.md docs: précautions de mise à jour prod (sauvegarde d'abord, jamais down -v) 2026-07-19 00:07:24 +02:00
SECURITY.md validation(formulaires): couche guide 5 (audit #18-#29) 2026-07-29 01:13:48 +02:00
tsconfig.json Initial commit from Create Next App 2026-06-04 22:20:37 +02:00

LightOnNails

LightOnNails is the complete website for a nail salon, live at lightonnails.online. It covers everything a client needs — browsing services, booking an appointment, paying online, joining the VIP club, sharing nail photos — plus a full admin panel for the salon owner. The entire interface is in French, with a bright summer theme and a gold "luxe" variant for VIP members.

The public site

Home — hero section with animated summer nails, feature highlights, latest news, and structured data (JSON-LD) for local-business SEO.

Prestations — the service menu: nails, nail art, and foot care, each with duration and price (VIP prices shown when applicable).

Réservation — a step-by-step booking wizard: pick a service, pick a one-hour slot on the calendar, confirm. Slots are overbooking-proof, and a waitlist is available when a day is full. Payment can be made online by card, from the wallet, or at the salon. A confirmation page and email follow every booking.

Cartes cadeaux — buy a gift card for someone: choose the amount, the recipient receives a code by email and credits it to their wallet.

Club VIP — memberships of 1, 3, 6, 9, or 12 months (prepaid or auto-renewed) with preferential prices, priority booking, exclusive news, and a gold luxury theme across the site.

Galerie — a community gallery: clients upload photos of their nails, everyone votes with likes, and a "creation of the month" ranking runs monthly (with an all-time top filter). Submissions are moderated before publication.

Avis — verified client reviews (only after a completed appointment), with star ratings and review structured data for search engines.

Actualités — news, promotions, and behind-the-scenes posts written in Markdown, each either public or VIP-only.

Newsletter — subscription with double opt-in confirmation and one-click unsubscribe.

Legal pages — CGU, CGV, mentions légales, and privacy policy, all editable from the admin panel.

Client accounts

  • Sign-up with email verification, login, password reset, and optional two-factor authentication (TOTP).
  • Mon espace — upcoming and past appointments, with the ability to reschedule up to 24 h before the slot (no client-side cancellation; inside 24 h the appointment is due).
  • Portefeuille — a wallet credited by codes or gift cards and usable for any payment on the site, with full transaction history.
  • Sécurité — password change and 2FA setup.
  • Mes données — GDPR tools: export all personal data or delete the account.

Admin panel

A dedicated /admin area for the salon owner: dashboard, statistics with charts, appointment management, client records, services, opening hours and closed dates, secure code and gift-card generation, VIP member management, news publishing, newsletter sending, gallery moderation, review moderation, payment overview, audit log, legal-page editing, and site settings (branding, business info).

Design & SEO

Responsive, mobile-first layout; summer color palette with the VIP gold variant; French-language UI throughout; per-page metadata, JSON-LD (local business + reviews), sitemap.xml, and robots.txt. Visitor analytics use a cookieless, GDPR-friendly Setka (Umami) tag that loads only on the production build.

Local development

Prerequisites: Node 22 and Docker (for Postgres).

# 1. Install dependencies
npm install

# 2. Configure the environment
cp .env.example .env
# then edit .env — at minimum set POSTGRES_* / DATABASE_URL and generate the
# three secrets (the command is at the top of .env.example). For local testing
# without Stripe/SMTP you can leave those blank.

# 3. Start only the database (the app runs on the host in dev)
docker compose up -d db

# 4. Apply migrations and seed (idempotent)
npm run db:migrate
npm run db:seed

# 5. Run the dev server
npm run dev            # http://localhost:3000

Handy scripts: npm run typecheck, npm run lint, npm run db:studio, npm run db:reset.

The seed creates the first administrator. In development it defaults to admin@lightonnails.fr / ChangeMe!2026 (override with SEED_ADMIN_EMAIL / SEED_ADMIN_PASSWORD). In production the seed refuses the default and requires a strong SEED_ADMIN_PASSWORD — change it from /admin after first login.

Hosting & production

The whole stack — app, Postgres, migrations, cron scheduler, and a Caddy HTTPS front-end — ships in docker-compose.yml. Caddy automatically obtains and renews a Let's Encrypt certificate for the domain, so no manual TLS setup is required.

Prerequisites: a Linux server with Docker and Docker Compose, ports 80 and 443 open to the internet, and the domain's DNS A record (and AAAA if you have IPv6) pointing at the server's public IP.

First deploy

# 1. Clone the repo onto the server, then create the environment file
cp .env.example .env

# 2. Edit .env and set, at minimum:
#    - POSTGRES_PASSWORD           (a strong database password)
#    - SESSION_SECRET, CODE_HMAC_SECRET, CRON_SECRET
#          each: node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))"
#    - DOMAIN=lightonnails.online  and  LETSENCRYPT_EMAIL=you@example.com
#    - APP_URL / NEXT_PUBLIC_APP_URL = https://lightonnails.online
#    - SEED_ADMIN_EMAIL / SEED_ADMIN_PASSWORD  (strong; used for the first /admin login)
#    - DATABASE_URL — keep the lon:… host part as "localhost" only for host-side CLI;
#      the containers build their own DATABASE_URL from POSTGRES_* automatically.

# 3. Build and launch the full stack (migrations + seed run once, then the app)
docker compose up -d --build

# 4. Watch it come up
docker compose ps
docker compose logs -f caddy   # first cert issuance appears here

Once DNS resolves and ports 80/443 are reachable, https://lightonnails.online serves over HTTPS with www redirecting to the apex. Log in at /admin with the seed admin credentials and change the password immediately.

Notes

  • Runtime source of truth for URLs is APP_URL (links, emails, sitemap, Stripe redirects) — always https://lightonnails.online in production.
  • TRUSTED_PROXY_HOPS=1 matches the single bundled Caddy proxy, so rate limiting sees the real client IP. Change it only if you add more proxies in front.
  • Bring your own proxy? If you already run Traefik / Nginx Proxy Manager, delete the caddy service from docker-compose.yml and point your proxy at the app on 127.0.0.1:3000.
  • Stripe (optional): webhook endpoint https://lightonnails.online/api/stripe/webhook; enable exactly these events: checkout.session.completed, checkout.session.async_payment_succeeded, invoice.paid, customer.subscription.deleted. Without Stripe keys, online card payment is hidden but the wallet/code and pay-at-salon options still work.
  • SMTP (recommended): without it, transactional emails — booking confirmations, password resets, gift-card codes, reminders — are logged instead of sent. Set the SMTP_* vars before real launch so password reset and confirmations actually reach clients. Two mailboxes are supported on the same server: SMTP_USER (no-reply@) for transactional mail and an optional SMTP_NEWSLETTER_USER (newsletters@) for marketing broadcasts — each authenticates as itself so the From matches.
  • Uploaded images persist in the uploads volume; business data in db_data; TLS certificates in caddy_data. Back these up (see below) — losing caddy_data just re-issues certs, but losing db_data/uploads loses data.
  • SESSION_SECRET and CODE_HMAC_SECRET are required (≥16 chars — the app refuses to start otherwise), as is a strong SEED_ADMIN_PASSWORD.

Backups

Both volumes should be backed up regularly:

# Database (nightly recommended)
docker compose exec -T db pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB" > backup-$(date +%F).sql
# Uploaded images
docker run --rm -v lightonnails_uploads:/data -v "$PWD":/backup alpine \
  tar czf /backup/uploads-$(date +%F).tar.gz -C /data .

Restore the database with psql, and the uploads by extracting the tarball back into the volume.

2FA lockout recovery (admin)

If the sole administrator loses their authenticator, clear 2FA directly in the database, then log in and re-enroll:

docker compose exec db psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" \
  -c "UPDATE \"User\" SET \"totpSecret\"=NULL, \"totpEnabledAt\"=NULL WHERE email='admin@lightonnails.online';"