Mobile onboarding companion for Quackteow Group staff, based on QT-HR-OPS-001 (30 · 60 · 90 Day Integration Programme).
Built with React, TypeScript, Tailwind CSS, and Capacitor for Android and iOS.
- Interactive roadmap dashboard with phase timeline
- Role-filtered checklists (Cook, Cashier, Supervisor)
- Self-registration with HR approval workflow
- Web admin console at
/admin(registrations, users, hire progress, programme CMS) - Live programme editor backed by Supabase (tasks, resources, milestones, outlets)
- Role-based admin navigation (admin, supervisor, GM, HR Ops, EXCO)
- Progress synced to Supabase (with local offline cache on device)
- Resource hub with SOP links
- Milestone reminders (Day 7, 15, 30, 45, 60, 90)
- Native Android and iOS shells with splash screen, status bar, and haptics
- Create a dedicated Supabase project for Quacksters.
- Apply migrations:
# Using Supabase CLI (recommended)
supabase db push
# Or run each file in supabase/migrations/ via the SQL editor, in order.Key migrations:
| Migration | Purpose |
|---|---|
20260519000000_quacksters_auth_schema.sql |
Auth, profiles, hire progress |
20260519140000_programme_content.sql |
Programme tables + get_programme() |
20260519140100_programme_seed.sql |
Seed from QT-HR-OPS-001 mock data |
20260519140200_programme_admin_rpcs.sql |
Admin write RPCs for programme CMS |
20260519150000_outlets_and_audit.sql |
Outlets config + admin audit log |
20260520100000_audit_hr_actions_reorders_password_reset.sql |
HR auditing, user update audit/password flag, programme list reorders |
20260520100500_fix_admin_list_users_signature.sql |
Drops and recreates admin_list_users (new column; PG forbids REPLACE on row-type change) |
20260520103000_audit_log_filters_complete_password_change.sql |
Audit log RPC: filters + offset paging; complete_password_change() for audited self‑service password clears |
After pulling this migration, run supabase db push on your project (staging first), then smoke-test Admin → Audit (filters, load more, CSV) and forced password reset → new password → profile no longer flagged.
Regenerate the programme seed after editing mock data:
npm run generate:programme-seed- Copy environment variables:
cp .env.example .env
# Set VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY from your project settings-
Bootstrap the first admin (see
supabase/seed.sql):- Create a user in Supabase Auth
- Run the SQL update to set
access_role = 'admin'
-
In Supabase Auth settings, disable email confirmation for local dev (optional).
Without .env, the app runs in offline mode with local-only profile setup (legacy behaviour). A banner appears when programme content falls back to cached/mock data.
| Route | Purpose |
|---|---|
/register |
Self-register (pending HR approval) |
/login |
Sign in |
/reset-password |
Required password change when password_reset_required |
/pending |
Awaiting approval / rejection status |
/ |
Mobile onboarding app (approved hires) |
/admin |
Admin dashboard |
/admin/registrations |
Approve/reject queue (HR Ops, Admin) |
/admin/users |
User and role management (Admin, HR Ops, GM) |
/admin/hires/:id |
Hire onboarding progress detail |
/admin/checklist |
Manager checklist (Supervisor, GM) |
/admin/programme |
Programme CMS (Admin only) |
/admin/audit |
Audit log (Admin + HR Ops) |
| Role | Nav items |
|---|---|
admin |
Dashboard, Programme, Audit log, Registrations, Users |
supervisor |
Dashboard, My checklist |
gm |
Dashboard, My checklist, Users |
hr_ops |
Dashboard, Audit log, Registrations, Users |
exco |
Dashboard (read-only) |
| Role | Description |
|---|---|
pending |
Registered, awaiting approval |
hire |
New hire — mobile onboarding |
supervisor |
Outlet supervisor (SUP) |
gm |
General Manager |
exco |
Executive Committee (read-only admin) |
hr_ops |
HR Operations — registration approval |
admin |
Full system admin |
Job roles (cook, cashier, supervisor) are separate and control checklist filtering.
npm install
npm run devOpen the URL shown in the terminal (usually http://localhost:5173).
| Command | Purpose |
|---|---|
npm run dev |
Local dev server |
npm run build |
Production build |
npm run lint |
ESLint |
npm run test |
Vitest unit tests |
npm run test:e2e |
Playwright smoke test (starts preview server) |
./scripts/run-android.sh |
Clean + installDebug + launch MainActivity (needs adb device) |
./scripts/run-ios.sh |
Sync + build for iOS Simulator + open Xcode (needs full Xcode) |
npm run cap:ios |
Build, sync, and open the iOS project in Xcode |
npm run generate:programme-seed |
Regenerate programme seed SQL from mock data |
The workflow .github/workflows/deploy-pages.yml builds the Vite SPA and publishes dist/ to GitHub Pages on every push to main or master (and can be run manually via Actions → Deploy GitHub Pages → Run workflow).
This repository (project site): the live app is at https://ezzademir.github.io/Quacksters/. The app uses HashRouter, so routes look like https://ezzademir.github.io/Quacksters/#/login (not /Quacksters/login — that avoids GitHub Pages 404 on refresh and deep links).
- Push this repository to GitHub.
- Settings → Pages → Build and deployment: set Source to GitHub Actions (not “Deploy from a branch”).
- Settings → Secrets and variables → Actions — add
VITE_SUPABASE_ANON_KEY:- Copy anon
publicfrom Supabase Dashboard → Project Settings → API. Use the JWT value starting witheyJ(same as local.env). Neverservice_role, never commit keys into git. VITE_SUPABASE_URLis optional for Pages: .github/workflows/deploy-pages.yml defaults tohttps://oivsccsntugiprfdnypn.supabase.co. Add this secret only if the deployed site should use a different Supabase project than that URL.
- Copy anon
Set the anon key from your machine with GitHub CLI (prompt hides input):
gh secret set VITE_SUPABASE_ANON_KEYIf VITE_SUPABASE_ANON_KEY is missing, the Pages build still succeeds but the site runs without Supabase (offline behaviour); the workflow prints a warning.
Confirm the live site: open Sign in. Under the form, Backend: should match Project URL in the dashboard (oivsccsntugiprfdnypn.supabase.co for the default URL).
If every login shows “does not match our records”: the build is almost certainly using a bad VITE_SUPABASE_ANON_KEY (GitHub Secret). Paste the anon public JWT (eyJ…) from Dashboard → API again — no wrapping quotes, no line breaks. Publishable sb_publishable_ keys often fail with password auth and RLS. After updating the secret, run Deploy GitHub Pages again and hard-refresh the site (or clear site data for github.io). The app also clears a broken local session before each sign-in attempt.
Seeing 400 (Bad Request) on …/auth/v1/token?grant_type=password in DevTools: that HTTP status is normal when GoTrue rejects a password grant (wrong password, unconfirmed email, captcha required, etc.). Open the failed request in Network → Response and read the JSON (error, error_code, msg) — that is the authoritative reason, not the status code alone.
If the URL secret and anon key secret are from different Supabase projects, the sign-in page shows an amber “Configuration issue” banner and the browser console logs [auth] details — fix secrets so both come from the same project.
| Repository type | Example URL | VITE_BASE_PATH in CI |
|---|---|---|
Project repo owner/repo |
https://owner.github.io/repo/ |
/<repo>/ |
Site repo named owner.github.io |
https://owner.github.io/ |
/ |
Vite reads VITE_BASE_PATH at build time; local/Android builds omit it so base stays ./ for Capacitor.
In the Supabase Dashboard → Authentication → URL configuration, allow the site URL (and redirect URLs) Supabase will send users to. This app uses HashRouter and password-reset emails use the SPA root as redirect_to (Supabase then appends #access_token=...&type=recovery). The app listens for PASSWORD_RECOVERY and navigates to #/recover-password.
Add explicit allowlist entries for this repo, for example:
https://ezzademir.github.io/Quacksters/http://localhost:5173/(local Vite dev, if testing reset mail there)
Do not rely on path-only URLs like /Quacksters/recover-password for email redirects; GitHub Pages would 404 on those without hash routing.
After reset, users complete the flow at .../Quacksters/#/recover-password inside the SPA.
Installing Android release APK (Release build) bundles web assets from npm run cap:sync at build time. Updating Pages does not update installed APKs unless you add a separate live-update mechanism.
After building android/app/build/outputs/apk/release/app-release.apk locally, open GitHub → Releases → Draft a new release, attach the APK, and publish. Use a private repository if the binary must stay internal-only.
Automating signed APK builds in Actions requires storing keystore material as encrypted secrets—left as a future enhancement unless your team standardizes it.
Step-by-step from clone to app-debug.apk on an emulator or USB device: docs/android-apk-walkthrough.md. With a device connected (adb devices), ./scripts/run-android.sh performs clean + install + launch.
- Android Studio with SDK installed
- JDK (bundled with Android Studio)
source scripts/android-env.shDefault SDK path: ~/Library/Android/sdk
npm run cap:syncnpm run cap:androidsource scripts/android-env.sh
cd android && ./gradlew installDebug
adb shell am start -n com.quackteow.onboarding/.MainActivityStep-by-step from clone to running on simulator or device: docs/ios-walkthrough.md. With full Xcode installed, ./scripts/run-ios.sh syncs web assets, builds for the default simulator, and opens the project.
- Xcode from the Mac App Store (not Command Line Tools alone)
- Apple ID for signing (free account works for device testing)
Point the active developer tools at Xcode:
sudo xcode-select -s /Applications/Xcode.app/Contents/Developernpm run cap:syncnpm run cap:ios- Open
ios/App/App.xcodeprojin Xcode (or usenpm run cap:ios). - Select a simulator or connected iPhone.
- Under Signing & Capabilities, choose your Team.
- Press Run (▶).
For TestFlight or App Store distribution, use Product → Archive in Xcode.
- Generate a signing keystore:
chmod +x scripts/generate-keystore.sh
./scripts/generate-keystore.sh- Copy and edit signing config:
cp android/keystore.properties.example android/keystore.properties
# Update passwords in keystore.properties- Build release APK:
npm run cap:releaseOutput: android/app/build/outputs/apk/release/app-release.apk
For Play Store, use ./gradlew bundleRelease to produce an AAB.
| Field | Value |
|---|---|
| App name | Quacksters |
| Package ID | com.quackteow.onboarding |
| Version | 1.0.0 (Android versionCode / iOS build 2) |
src/
├── components/
│ ├── admin/ # Web admin console + programme editor
│ ├── auth/ # Login, register, route guards
│ ├── dashboard/ # Mobile dashboard
│ ├── onboarding/ # Welcome / hire confirmation
│ └── ...
├── context/ # Auth, outlets, programme, onboarding state
├── data/ # Fallback roadmap data from QT-HR-OPS-001
├── lib/ # Supabase, permissions, programme API, storage
└── types/ # TypeScript interfaces
supabase/
├── migrations/ # Database schema + RLS + programme RPCs
└── seed.sql # Bootstrap admin instructions
android/ # Capacitor Android project
ios/ # Capacitor iOS project
scripts/ # android-env.sh, run-android.sh, run-ios.sh, generate-keystore.sh, generate-programme-seed.mjs
GitHub Actions runs ESLint, Vitest, production build, and a Playwright smoke test on push/PR to main or master.
A separate workflow Deploy GitHub Pages (see Deploy web app (GitHub Pages)) publishes the SPA to Pages on pushes to main/master when Pages is enabled.
When developing locally: npx playwright install chromium once if npm run test:e2e fails to find browsers.
Internal use only. Aligned to QT-SOP-OPS-002 v3.0.