React + FastAPI + Stripe + Admin Dashboard + Telegram Bot
Live Demo: https://frontend-production-4bd7.up.railway.app/
GitHub: https://github.com/EdvinZe/saloon
A deployed full-stack booking and operations platform for salons/barbershops. The system includes customer booking flow, Stripe test payments, webhook/fallback confirmation, customer manage booking flow, protected admin APIs, Telegram staff operations, backend tests, CI, and practical security hardening.
FastAPI is the source of truth for availability, booking rules, payment confirmation, admin actions, and staff workflows.
The current deployment target is a portfolio/demo environment on Railway using Docker, Stripe test mode, and SQLite with a persistent volume. For real production use, the same architecture can be extended with PostgreSQL, migrations, queue-backed notifications, monitoring, and production email infrastructure.
AI Booking Assistant: The platform includes a conversational AI assistant built around one core principle: AI understands, backend verifies, user chooses. The AI layer is read-only — it never writes to the database, creates bookings, or bypasses business rules; the FastAPI backend is the source of truth for all availability and booking state. The model returns a structured intent (service, master, date, time), not free text; the backend validates that intent against real data before building any response. See ai_readme_section.md for the full AI architecture.
- Browse active services and masters.
- Select date and time from backend-calculated availability.
- Pay a deposit with Stripe PaymentElement in test mode.
- Success, delayed confirmation, and error handling after payment.
- Booking confirmation email with appointment details.
- Secure manage booking link.
- Reschedule or cancel by manage token, subject to backend cutoff rules.
- FastAPI API with SQLAlchemy models and SQLite storage.
- Backend is the source of truth for services, masters, schedules, availability, conflicts, and payment amounts.
- Stripe PaymentIntent creation with server-side amount calculation.
- Stripe webhook route for
payment_intent.succeeded. - Fallback confirmation endpoint that verifies the PaymentIntent directly with Stripe if the webhook is delayed or unavailable.
- Idempotent booking creation keyed by Stripe PaymentIntent ID to prevent duplicate bookings.
- Email and Telegram notifications are best-effort side effects after booking creation.
- Core operational entities use status or active flags where applicable instead of hard deletion.
- Protected admin login.
- Services management.
- Masters management.
- Schedule management.
- Bookings management.
- Reports.
- Telegram account management.
- Booking status workflows such as cancel, refund, complete, and no-show where supported by the admin views.
- aiogram 3 bot located in
backend/telegram_bot. - Manager and barber role-based access.
TelegramAccountrecords in the database are the source of truth for access and routing.- The bot calls the FastAPI backend and does not access the database directly.
- Commands such as
/today,/tomorrow, and/now. - Manager summaries.
- Same-day booking notifications.
- Barber appointment reminders.
- Unknown or inactive Telegram users are denied access.
- Docker support for frontend and backend.
- Railway-ready service layout.
- Separate deployable services for frontend, backend API, and Telegram bot.
- SQLite volume support for demo deployments.
- Stripe test mode support.
The assistant understands natural-language requests, extracts booking intent as a structured output, and guides users into the normal booking flow. It is read-only: no booking is created, no payment is initiated, and no business rule is bypassed inside the assistant layer.
What it can do:
- Answer questions about active services and masters
- Extract booking intent from natural language (service, master, date, time)
- Search exact and flexible availability through the backend availability engine
- Handle typo-tolerant weekday and relative date input
- Return prefill buttons that open the booking form pre-populated with details
- Fall back gracefully to the booking form if the AI provider is unavailable
What it cannot do:
- Create a booking or Stripe PaymentIntent
- Access admin-only or private data
- Mutate the database
- Bypass backend availability validation or claim a slot is available without a backend check
Request flow:
Customer message → POST /api/ai/booking-intent
→ Local shortcuts checked first (greetings, start over, show more)
→ AI provider extracts structured intent (intent type, service, master, date, time)
→ Backend resolves real service/master records and checks availability if needed
→ Backend generates factual response from verified data
→ Frontend renders assistant message and prefill action buttons
→ User clicks prefill or opens booking form → normal booking and Stripe payment flow
Layer responsibilities:
| Layer | Responsibility |
|---|---|
| AI layer | Language understanding and intent extraction |
| Backend | Data validation, availability, and business rules |
| Database | Source of truth for all entities and state |
See ai_readme_section.md for architecture detail, supported inputs, safety boundaries, provider configuration, and the full module reference.
- React
- Vite
- TypeScript
- React Router
- Stripe.js and Stripe PaymentElement
- FastAPI
- SQLAlchemy
- SQLite
- PyJWT admin sessions
- Stripe Python SDK
- Resend transactional email API
- aiogram 3
- httpx
- Docker
- Railway
The frontend is a client application. It does not own booking, payment, schedule, or availability rules. The Telegram bot is also a client/worker and communicates through backend APIs. The FastAPI backend is the source of truth.
Stripe webhook handling is implemented as a backend route, not as a separate service. Email and Telegram notifications happen after booking creation and are treated as side effects so notification failures do not break the booking flow.
Admin APIs are protected by admin authentication. Public booking and manage-token endpoints remain separate from admin APIs. Bot endpoints are separate and validate bot access using the bot token and TelegramAccount authorization rules.
Customer Browser
-> Frontend
-> FastAPI Backend
-> SQLite
-> Stripe API
-> Resend Email API
-> Telegram Bot API
Stripe
-> /api/stripe/webhook
Telegram Bot Service
-> FastAPI Backend
The frontend is located at the repository root. The backend and Telegram bot are located under backend/.
.
├── Dockerfile
├── package.json
├── src/
├── public/
├── backend/
│ ├── Dockerfile
│ ├── requirements.txt
│ ├── app/
│ │ ├── api/
│ │ ├── core/
│ │ └── modules/
│ └── telegram_bot/
└── README.md
Do not commit .env files. Use placeholders in examples and configure real values in local .env files or Railway service variables.
Vite environment variables are build-time variables. Backend and bot variables are runtime variables.
Only Vite-exposed, browser-safe variables belong in the root .env.
VITE_API_BASE_URL=https://backend-url
VITE_STRIPE_PUBLISHABLE_KEY=pk_test_...Backend runtime variables and secrets belong in backend/.env, not the root .env.
APP_ENV=development
LOG_LEVEL=INFO
DATABASE_URL=sqlite:///./saloon.db
AUTO_CREATE_TABLES=true
PUBLIC_FRONTEND_URL=http://localhost:5173
CORS_ALLOWED_ORIGINS=http://localhost:5173
BACKEND_API_URL=http://127.0.0.1:8000
CLIENT_MANAGE_CUTOFF_HOURS=12
RATE_LIMIT_ENABLED=true
AI_ENABLED=true
AI_PROVIDER=groq
GEMINI_API_KEY=
GEMINI_MODEL=gemini-2.5-flash
GROQ_API_KEY=
GROQ_MODEL=llama-3.1-8b-instant
AI_DAILY_REQUEST_LIMIT=50
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
RESEND_API_KEY=your_resend_api_key
EMAIL_FROM=onboarding@resend.dev
EMAIL_FROM_NAME=Saloon Booking
ADMIN_USERNAME=
ADMIN_PASSWORD_HASH=
ADMIN_PASSWORD=
ADMIN_SESSION_SECRET=
ADMIN_SESSION_EXPIRE_MINUTES=1440
TELEGRAM_BOT_TOKEN=
TELEGRAM_AUTH_SOURCE=db
TELEGRAM_MANAGER_IDS=
TELEGRAM_BARBER_MASTER_MAP=
BOT_TIMEZONE=Europe/Vilnius
BARBER_REMINDER_MINUTES=15
BARBER_REMINDER_CHECK_INTERVAL_SECONDS=60TELEGRAM_BOT_TOKEN=
BACKEND_API_URL=https://backend-url
TELEGRAM_AUTH_SOURCE=db
TELEGRAM_MANAGER_IDS=
TELEGRAM_BARBER_MASTER_MAP=
BOT_TIMEZONE=Europe/Vilnius
BARBER_REMINDER_MINUTES=15
BARBER_REMINDER_CHECK_INTERVAL_SECONDS=60The Stripe webhook secret for a deployed Railway backend is different from the local Stripe CLI webhook secret. Configure each environment with the correct STRIPE_WEBHOOK_SECRET.
The backend includes a provider-agnostic AI assistant for the booking chat widget. Supported providers are Groq and Gemini. Set AI_PROVIDER to groq or gemini, configure the matching API key in backend/.env or Railway service variables, and leave the other provider's key empty. Never expose AI API keys in the frontend root .env.
The assistant answers service and master questions, extracts booking intent from natural language, searches flexible availability, and returns prefill actions that open the normal booking form. The AI layer is read-only: it does not create bookings, payments, refunds, or admin changes, and does not mutate the database. If the provider is unavailable the booking system continues to work normally.
See ai_readme_section.md for the full architecture, request flow, supported inputs, safety boundaries, and environment reference.
npm install
npm run devcd backend
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
uvicorn app.main:app --reloadcd backend
source .venv/bin/activate
pytest
python -m compileall app telegram_botThe backend test suite includes FastAPI HTTP integration tests. Tests use temporary SQLite databases and mock external notification/payment edges; they do not use Railway data or call Stripe, Resend, or Telegram.
npm run lint
npm run buildGitHub Actions runs the frontend lint/build checks and backend pytest/compile checks on pushes and pull requests to main and master.
cd backend
source .venv/bin/activate
python -m telegram_bot.mainstripe listen --forward-to localhost:8000/api/stripe/webhookFor local testing, configure backend/.env with test keys and local URLs. Stripe test mode is expected.
If the local webhook is not running, the payment-result fallback can still confirm payment after the customer returns to the success page. The webhook is still important because a customer may close the browser before the success page loads.
To fill demo schedules for masters 1-3 through 2028-12-31:
cd backend
source .venv/bin/activate
python scripts/seed_demo_schedule.pyThe command creates working schedules for the demo masters: master 1 works every day except Sunday from 15:00 to 20:00, master 2 works every second day except Sunday from 10:00 to 18:00, and master 3 works every day except Sunday from 09:00 to 20:00.
The script is idempotent and safe to rerun. If a shift already exists for the same master and date, it updates that row to the demo values or skips it when unchanged. It does not delete schedules, bookings, services, or masters.
Dockerfiles are provided for deployment.
docker build -t saloon-frontend .
docker run --rm -p 3000:3000 -e PORT=3000 saloon-frontendThe frontend Docker image builds the Vite app and serves the generated dist folder with SPA fallback support, so browser refreshes on routes such as /admin/login or booking manage pages still load index.html.
docker build -t saloon-backend ./backend
docker run --rm -p 8000:8000 \
-e PORT=8000 \
-e DATABASE_URL=sqlite:///./saloon.db \
saloon-backendThe default backend command is:
uvicorn app.main:app --host 0.0.0.0 --port ${PORT:-8000}The Telegram bot can reuse the backend image with a command override:
python -m telegram_bot.mainThe intended Railway setup uses separate services for the frontend, backend API, and Telegram bot.
- Root directory:
/or repository root. - Dockerfile:
Dockerfile. - Public domain enabled.
- Environment variables:
VITE_API_BASE_URL=https://backend-url
VITE_STRIPE_PUBLISHABLE_KEY=pk_test_...- Root directory:
/backend. - Dockerfile:
Dockerfile. - Public domain enabled.
- Persistent volume mounted at
/data. - SQLite demo database:
DATABASE_URL=sqlite:////data/saloon.db
APP_ENV=production
LOG_LEVEL=INFO
AUTO_CREATE_TABLES=true
PUBLIC_FRONTEND_URL=https://frontend-url
CORS_ALLOWED_ORIGINS=https://frontend-url
BACKEND_API_URL=https://backend-url
CLIENT_MANAGE_CUTOFF_HOURS=12
RATE_LIMIT_ENABLED=true
AI_ENABLED=true
AI_PROVIDER=groq
GEMINI_API_KEY=
GEMINI_MODEL=gemini-2.5-flash
GROQ_API_KEY=
GROQ_MODEL=llama-3.1-8b-instant
AI_DAILY_REQUEST_LIMIT=50
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
ADMIN_USERNAME=
ADMIN_PASSWORD_HASH=
ADMIN_PASSWORD=
ADMIN_SESSION_SECRET=
ADMIN_SESSION_EXPIRE_MINUTES=1440
RESEND_API_KEY=your_resend_api_key
EMAIL_FROM=onboarding@resend.dev
EMAIL_FROM_NAME=Saloon Booking
TELEGRAM_BOT_TOKEN=
TELEGRAM_AUTH_SOURCE=db
TELEGRAM_MANAGER_IDS=
TELEGRAM_BARBER_MASTER_MAP=
BOT_TIMEZONE=Europe/Vilnius
BARBER_REMINDER_MINUTES=15
BARBER_REMINDER_CHECK_INTERVAL_SECONDS=60- Root directory:
/backend. - Dockerfile:
Dockerfile. - Public domain not required.
- Start command:
python -m telegram_bot.main- Environment variables:
TELEGRAM_BOT_TOKEN=
BACKEND_API_URL=https://backend-url
TELEGRAM_AUTH_SOURCE=db
TELEGRAM_MANAGER_IDS=
TELEGRAM_BARBER_MASTER_MAP=
BOT_TIMEZONE=Europe/Vilnius
BARBER_REMINDER_MINUTES=15
BARBER_REMINDER_CHECK_INTERVAL_SECONDS=60Configure the Stripe webhook endpoint to:
https://backend-url/api/stripe/webhook
- Deploy the backend service.
- Add the backend public domain.
- Deploy the frontend service with
VITE_API_BASE_URLpointing to the backend. - Update backend
PUBLIC_FRONTEND_URLwith the frontend URL. - Deploy the Telegram bot service with
BACKEND_API_URLpointing to the backend. - Configure the Stripe webhook endpoint.
- Update
STRIPE_WEBHOOK_SECRETin Railway. - Run a full smoke test.
The public demo focuses on the customer booking flow and Stripe test payment. The admin panel is protected and is not intended to be publicly shared. The Telegram bot is protected by Telegram ID and role-based account records. Admin and bot workflows can be demonstrated by video or screen share.
The deployed demo uses Stripe test mode for portfolio/demo payments. Use the following Stripe test payment details; no real money is charged.
Card number: 4242 4242 4242 4242
Expiry date: any future date, for example 12/34
CVC: any 3 digits, for example 123
ZIP/postal code: any valid value, for example 12345
Payment mode: Stripe test mode / demo only
Emails are sent through the configured Resend transactional email API over HTTPS. Configure real Resend values in Railway service variables; no email provider secrets are committed. Email delivery is treated as a notification side effect, so a provider failure is logged but does not block booking creation or payment confirmation. SQLite is used for demo simplicity; AUTO_CREATE_TABLES=true lets the demo ensure tables at startup until migrations are added. PostgreSQL with migrations and backups is recommended for real production deployments.
- Frontend opens.
- Services and masters load.
- Slots load from backend availability.
- PaymentIntent is created with backend-calculated amount.
- Stripe test payment succeeds.
- Booking is created.
- Confirmation email is sent.
- Manage link works.
- Admin panel shows the booking.
- Telegram notification is sent to the appropriate operational account.
- Replaying the webhook does not duplicate the booking.
- Running fallback confirmation after webhook does not duplicate the booking.
- Running fallback confirmation without webhook creates the booking.
- Manage cancel and reschedule respect backend cutoff rules.
The FastAPI backend is the source of truth for booking, payment, admin, and staff workflows. The frontend and Telegram bot are clients; they do not own availability, pricing, payment confirmation, authorization, or booking state transitions.
Implemented safeguards:
- Booking and payment logic are enforced server-side, including service duration, master/service compatibility, schedule rules, slot conflicts, and deposit amount calculation.
- Slot availability is checked before creating a Stripe PaymentIntent and re-validated before final booking confirmation.
- Stripe webhooks verify signatures, duplicate webhook deliveries do not create duplicate bookings, and booking creation is idempotent by Stripe PaymentIntent ID.
- The payment-result fallback verifies a PaymentIntent directly with Stripe if the webhook is delayed or unavailable.
- Manage-token booking access is separated from admin APIs. Manage endpoints do not echo the raw
manage_tokenback in manage responses, and cancel/reschedule actions enforce backend cutoff rules. - Admin authentication supports hashed password verification and HTTP-only session cookies. Admin routes are protected behind the session check.
- Telegram bot backend endpoints require the bot secret header and apply
TelegramAccountauthorization rules. Barber accounts are scoped to their own bookings, while manager accounts can see all bookings. - Risk-based in-memory rate limiting applies per client IP and bucket for public availability, payment, manage-token, admin login, and Telegram bot endpoints. This is suitable for the demo/single-instance deployment; Redis or another shared limiter would be the next upgrade for multi-instance production.
- Basic security headers, strict CORS configuration, and a 1MB request body limit are applied by the backend.
- Secrets and deployment-specific settings are environment-based. Do not commit
.envfiles,saloon.db, or real provider credentials. - Backend tests use isolated temporary SQLite databases and mock external Stripe, email, and Telegram edges. GitHub Actions checks backend tests/compile plus frontend lint/build.
Future production upgrades would include PostgreSQL with Alembic migrations, database-backed admin/staff accounts with roles, queue-backed notifications, monitoring/log redaction, production email infrastructure, and shared rate limiting.
- SQLite is used for demo deployment.
- Admin and bot tools are protected operational surfaces, not public demo pages.
- SMS notifications are not included.
- Notifications are not backed by a dedicated queue such as Celery.
- Reminder duplicate protection is MVP-level.
- The project can be extended for production with PostgreSQL, Alembic migrations, queued notifications, monitoring, log masking, and provider-grade email delivery.
- PostgreSQL.
- Alembic migrations.
- Expand automated test coverage.
- Persistent notification logs.
- Production transactional email provider.
- Custom domain.
- Monitoring and log masking.
- Deployment automation.
- Role-based admin users.
- Mobile app client.
This project is intended as a portfolio/demo project.