About this software
About Glowhub – Salon & Spa Booking SaaS
Glowhub is a Laravel 12 + Vue 3 SaaS that gives salons and spas their own online booking website (subdomain or custom domain) and a dashboard to manage appointments, staff, services, customers, payments and payroll. Every tenant shares one MySQL database (organization_id scoped). The public booking flow is a 4-step wizard (service → staff → date/time → details) with SSLCommerz payment integration and automated SMS/WhatsApp reminders via Twilio. The owner dashboard includes a month/week/day calendar, staff rosters with per-staff services and working hours, a service catalogue with categories, customer records auto-created on first booking, expense tracking, payroll runs with commission calculation, and revenue reports. The platform supports multiple branches per salon (free plan: 1 branch, 10 staff), role-based access (owner/manager/staff), email verification, password reset, and a cross-tenant salon search for customers who don't know a salon's URL.
Why Glowhub – Salon & Spa Booking SaaS
What makes it better
than the alternatives
- 01
Multi-tenant architecture
- 02
Pluggable payment gateway
- 03
Automated reminders
- 04
Staff payroll with commission
- 05
Subdomain + custom domain support
Authentication & Multi-Tenancy
- Register (AuthController::register
- RegisterOrganization action) creates User
- Organization
- Domain
- Branch
- Setting and PaymentSetting in one transaction
- Login with email/password (AuthController::login)
- rate-limited per (email
- IP) with 5 attempts per 60s
- returns Sanctum token + user + organization
- Password reset (PasswordResetController
- Password::broker()->sendResetLink
- ResetPassword notification with FRONTEND_URL link)
- Email verification (EmailVerificationController
- User implements MustVerifyEmail
- VerifyEmail notification)
- Tenant resolution (ResolveTenant middleware reads organization_id from user->organization
- ResolvePublicTenant reads slug from Host header or /
salon/:slug path) - Role-based access (UserRole enum:
owner/manager/staff - policies in
app/Policies/ - canManageOperations computed in auth store)
- Organization status guard (OrganizationStatus enum:
active/suspended/inactive - AuthController::login refuses non-active
- tenant middleware 403s suspended)
Public Booking Site
- Salon shopfront (SalonSiteView.vue
- PublicSiteController::show returns organization + branches + services + staff + gallery + reviews)
- 4-step booking wizard (PublicBookingView.vue: service selection → staff selection → date/time picker → customer details
- PublicBookingController::store)
- Slot generation (SlotGenerator service
- respects staff working hours from staff_profiles.working_hours JSON
- branch closures from branch_closures table
- staff time-off from staff_time_offs table)
- Abandoned booking release (ReleaseAbandonedBookings command scheduled every 15 minutes
- AbandonedBookingReleaser service
- releases held slots after BOOKING_HOLD_MINUTES)
- Payment integration (SslcommerzGateway service
- PaymentCallbackController::
success/fail/cancel - stores Payment with status
pending/completed/failed - Deposit handling (DepositType enum:
none/fixed/percentage - PublicBookingRequest validates deposit_amount when required
- held until payment completes)
- Review submission (PublicReviewController::store
- creates Review with rating 1-5 + comment
- requires valid appointment public_token)
- Cross-tenant salon search (DiscoveryController::index
- searches organizations.name + branches.city + services.name
- returns paginated results)
Appointments & Calendar
- Appointment CRUD (AppointmentController
- AppointmentPolicy
- fields: customer_id
- branch_id
- start_time
- end_time
- status enum
pending/confirmed/completed/cancelled/no_show - notes
- public_token UUID)
- Multi-service appointments (AppointmentService pivot
- AppointmentServiceWriter action
- stores service_id + staff_id + price + duration per line)
Month/week/daycalendar (CalendarView.vue- AppointmentController::index with ?start_date=&end_date=
- returns appointments + staff + services)
- Drag-to-reschedule (calendar emits @update:appointment
- calls AppointmentController::update with new start_time
- recalculates end_time from total duration)
- Status transitions (AppointmentStatus enum
- completed appointments feed into payroll and reports
- cancelled/no_show release the slot)
- Booking notifications (BookingNotifier service
- queues BookingConfirmationMail + NewBookingMail on create
- BookingCancelledMail on cancel
- BookingRescheduledMail on reschedule)
- Invoice generation (AppointmentController::invoice
- returns appointment + services + payment + customer
- rendered in InvoiceView.vue)
Staff Management
- Staff CRUD (StaffController
- UserPolicy
- creates User with role=staff + StaffProfile
- fields: position
- bio
- profile_image
- working_days JSON array
- working_hours JSON {day: {start
- end}})
- Per-staff services (staff_profiles.service_ids JSON array
- StaffProfile::services() belongsToMany
- filtered in slot generation and booking flow)
- Staff time-off (StaffTimeOffController
- StaffTimeOffPolicy
- fields: staff_id
- start_date
- end_date
- reason
- blocks slots in SlotGenerator)
- Pay rules (staff_profiles.pay_type enum
hourly/salary/commission - base_pay decimal
- commission_rate decimal
- PayrollCalculator reads these)
- Staff-only dashboard access (isStaff computed in auth store
- router guards /staff routes
- StaffController::myAppointments returns own schedule)
Services & Categories
- Service CRUD (ServiceController
- ServicePolicy
- fields: name
- category_id
- duration minutes
- price
- description
- status enum active/inactive)
- Service categories (ServiceCategoryController
- fields: name
- description
- display_order
- categories.services hasMany)
- Bulk service creation (ServiceController::bulkStore
- BulkStoreServiceRequest
- creates multiple services in one request
- used in onboarding)
- Service presets
config/service_presets.php - arrays of {name
- duration
- price} per category
- ServiceController::presets returns them for quick setup)
- Service status toggle (ServiceStatus enum
- inactive services hidden from public booking but kept in historical appointments)
Customers
- Customer CRUD (CustomerController
- CustomerPolicy
- fields: name
- phone
- notes
- customer_account_id nullable FK)
- Auto-creation on booking (PublicBookingController creates Customer if email not found
- links to CustomerAccount if one exists with that verified email)
- Customer accounts (CustomerAccount model
- passwordless auth via CustomerLoginCode 6-digit OTP
- CustomerAuthController::requestCode/verifyCode)
- Account linking (CustomerAuthController::verifyCode updates all Customer rows with that email to set customer_account_id
- backfills name from any row)
- Customer portal (CustomerBookingController
- customer guard
- lists own appointments across all salons
- cancel/reschedule if allowed)
Payments
- Payment records (Payment model
- fields: appointment_id
- amount
- method enum
card/cash/other - status enum
pending/completed/failed/refunded - source enum online/in_person
- gateway_transaction_id
- tip_amount)
- SSLCommerz integration (SslcommerzGateway service
- PaymentSetting.credentials encrypted:array {store_id
- store_password
- sandbox}
- PaymentCallbackController handles
success/fail/cancel - Deposit collection (PublicBookingRequest validates deposit when organization.deposit_type is fixed/percentage
- held until appointment confirmed)
- Tip handling (PaymentController::addTip
- updates payment.tip_amount
- included in staff payroll commission calculation)
- Payment verification (PaymentController::verify
- calls gateway API to confirm transaction
- updates status to completed/failed)
Finance & Payroll
- Expense tracking (ExpenseController
- Expense model
- fields: category enum
supplies/rent/utilities/marketing/salary/other - amount
- date
- description
- receipt_path)
- Payroll runs (PayrollRunController
- PayrollRun model
- fields: period_start
- period_end
- status enum draft/finalized
- total_amount)
- Payroll calculation (PayrollCalculator service
- reads staff pay_type + base_pay + commission_rate
- sums completed appointments per staff
- creates PayrollLine per staff)
- Commission on services (PayrollCalculator::calculateCommission
- reads appointment_services.price * staff commission_rate
- adds to base pay)
- Payroll finalization (PayrollRunController::finalize
- locks the run
- creates Expense rows for each line
- updates status to finalized)
- Revenue reports (ReportService
- ReportController::index
- aggregates payments by
date/staff/service - filters by branch/date range)
Reminders & Notifications
- Reminder settings (ReminderSetting model
- fields: enabled boolean
- hours_before integer
- channels JSON array [sms
- email]
- credentials encrypted:array)
- Twilio integration (TwilioReminderChannel
- reads TWILIO_ACCOUNT_SID + AUTH_TOKEN + FROM + WHATSAPP_FROM from env or salon credentials
- sends via Twilio API)
- Reminder scheduling (SendAppointmentReminders command scheduled hourly
- AppointmentReminderService finds appointments due in X hours
- queues SendAppointmentReminder job per appointment)
- Pluggable channels (ReminderChannelManager
- resolves channel from config
- LogReminderChannel for local dev
- TwilioReminderChannel for production)
- Email reminders (BookingConfirmationMail implements ShouldQueue
- includes appointment details + salon address + cancel link)
Settings & Configuration
- Organization settings (OrganizationController::update
- UpdateOrganizationRequest
- fields: name
- phone
- address
- timezone
- currency
- theme_color
- logo_path)
- Branch management (BranchController
- Branch model
- fields: name
- address
- phone
- google_map_link
- opening_hours JSON {day: {open
- close}}
- free plan limited to 1 branch)
- Branch closures (BranchClosureController
- BranchClosure model
- fields: branch_id
- start_date
- end_date
- reason
- blocks all slots in SlotGenerator)
- Payment settings (PaymentSettingController::update
- UpdatePaymentSettingRequest
- stores gateway credentials in encrypted:array
- validates required fields per gateway)
- Reminder settings (ReminderSettingController::update
- UpdateReminderSettingRequest
- stores Twilio credentials in encrypted:array
- validates phone format)
- Gallery management (GalleryController
- Gallery model
- fields: title
- description
- image_path
- display_order
- shown on public salon site)
- Domain management (domains table
- fields: organization_id
- domain
- is_primary boolean
- nginx-salon.conf serves custom domains
- ResolvePublicTenant resolves by Host header)
| Language | Version | Framework | Used for | Share of code |
|---|---|---|---|---|
| PHP | — | — | Web, API | 58.1% |
| Vue | — | — | Front-end | 26% |
| JavaScript | — | — | Front-end | 13.5% |
| SQL | — | — | Database | 1.4% |
| Shell | — | — | Scripts | 0.8% |
| CSS | — | — | Styling | 0.3% |
| HTML | — | — | Front-end | 0% |
- Primary language
- PHP 8.4
- Framework
- Laravel 12 + Vue 3
- Database
- MySQL 8.0 or SQLite
- Authentication
- Laravel Sanctum (staff tokens) + passwordless OTP (customers)
- Licence
- Single-site
- Screens
- 42
- Database tables
- 23
- Supported languages
- English only
- Browser support
- Modern browsers (Chrome, Firefox, Safari, Edge — ES2020+)
| Minimum | Recommended | |
|---|---|---|
| CPU | 2 vCPU | 2 vCPU |
| RAM | 2 GB RAM / 20 GB SSD | 4 GB RAM / 40 GB SSD |
| OS | Ubuntu 24.04, PHP 8.4-FPM, MySQL 8.0, Redis 7, nginx, Node.js 22/24 (build only) | Ubuntu 24.04, PHP 8.4-FPM, MySQL 8.0, Redis 7, nginx, Node.js 22/24 (build only) |
| Est. monthly hosting | $12 | $12 |
```bash cp .env.example .env # Edit .env: set APP_KEY, DB_PASSWORD, APP_URL, APP_DOMAIN docker compose up -d docker compose exec app php artisan migrate --force docker compose exec app php artisan db:seed --class=DemoSalonSeeder ```
```bash # Follow backend/docs/deploy/README.md in full (9 steps) # Summary: sudo apt update && sudo apt install -y software-properties-common curl git unzip sudo add-apt-repository -y ppa:ondrej/php && sudo apt update sudo apt install -y php8.4-fpm php8.4-cli php8.4-mbstring php8.4-xml php8.4-curl php8.4-zip php8.4-mysql php8.4-redis mysql-server redis-server nginx supervisor sudo useradd -m -s /bin/bash deploy sudo mkdir -p /var/www/glowhub && sudo chown deploy:deploy /var/www/glowhub sudo -u deploy git clone <repo> /var/www/glowhub cd /var/www/glowhub/backend sudo -u deploy composer install --no-dev --optimize-autoloader sudo -u deploy cp docs/deploy/env.production.example .env # Edit .env: APP_KEY (php artisan key:generate), DB_*, APP_URL, APP_DOMAIN, MAIL_*, TWILIO_* sudo -u deploy php artisan migrate --force sudo -u deploy php artisan storage:link cd ../frontend && npm ci && npm run build # Copy backend/docs/deploy/nginx-*.conf to /etc/nginx/sites-available/, symlink to sites-enabled # Copy backend/docs/deploy/glowhub-worker.conf to /etc/supervisor/conf.d/ sudo systemctl reload nginx php8.4-fpm sudo supervisorctl reread && sudo supervisorctl update echo '* * * * * cd /var/www/glowhub/backend && php artisan schedule:run >> /dev/null 2>&1' | sudo -u deploy crontab - ```
```bash # See docs/demo-deploy.md # Create PostgreSQL database + Web Service on Render # Set environment variables in Render dashboard: # APP_KEY, DATABASE_URL, APP_URL, APP_DOMAIN, DEMO_MODE=true # Build: cd backend && composer install && cd ../frontend && npm ci && npm run build && cp -r dist ../backend/public/app # Start: cd backend && php artisan migrate --force && php artisan serve --host=0.0.0.0 --port=$PORT ```
```bash git clone <repo> glowhub && cd glowhub cp .env.example backend/.env cd backend && php artisan key:generate && touch database/database.sqlite php artisan migrate --seed cd ../frontend && npm install # Terminal 1: cd backend && php artisan serve # Terminal 2: cd backend && php artisan queue:listen # Terminal 3: cd frontend && npm run dev # Open http://localhost:5173 ```
Quality report
Manually reviewedHow to read the quality report
What this is. Every project on Vibe96 goes through an automated audit of its source code before a reviewer in Dubai checks it by hand. The score is out of 100; 75 is the minimum to be listed, and any single failing check blocks a listing regardless of the score.
- Security
- No real credentials in the code, every data-changing route protected, admin areas role-gated, no SQL built from user input.
- Code structure
- A developer can navigate and extend it: clear module boundaries, no giant files, little copy-paste.
- Docs & completeness
- The setup guide, example config, schema and required files the builder committed to are actually in the archive.
- Dependency health
- Packages pinned, no known critical vulnerabilities, no abandoned libraries in production paths.
- Build & deploy
- A clean machine can restore, build and start it by following the shipped instructions.
Warnings are problems that didn't block the listing; they stay visible here until a later version clears them. The audit reads the code — it does not run it — so always try the live demo before you buy. Lines of code are counted from the same audited archive.
Reviewer notes (4 minor)
Weak default credentials in example files; no real secrets found; no critical vulnerabilities; some outdated packages and missing frontend lockfile; multiple unscoped deletes, missing indexes on foreign keys, and raw sql in reports; build reproducible with gaps: missing node version in setup doc, unclear frontend env handling; moderate code duplication and some oversized files; structure generally sound. None of these stop the software from working; the developer has been told and improvements come in a future update.
Versions & updates
No updates yet. New versions appear here with a changelog.
Buyers receive updates free for 1 year.
What you get
- </>Full source codeThe complete audited source, delivered as a download from your account. No obfuscation.
- ▤DocumentationSetup guide, architecture notes, CLAUDE.md and AGENTS.md.
- ↻One year of updatesEvery new version the builder publishes, free for 12 months.
- ♧30 days of supportInstallation and defect support through Vibe96 tickets.