Note
The dashboard images come from the app's built-in preview mode, which renders sample data when no Supabase credentials are present. The landing page and sign-in are public pages. No real user data is shown anywhere.
ShiftSentry is a responsive workspace for people balancing one or more jobs. It brings scheduled shifts, completed work, pay, deductions, and weekly limits into one calm view—so users can spot a problem before it becomes one.
Hours are counted in your time zone and your week, not the server's. An overnight shift is split at local midnight and counted across both days, and every total—this week, this month, all time—is accumulated the same way, so the same shift is never worth a different amount in a different view.
Account protections now include explicit versioned Terms acceptance and 16+ eligibility, an account JSON export, and confirmed permanent deletion from /account. Privacy and storage disclosures are at /privacy, /terms, and /cookies. See the deduplicated screenshot audit and privacy/incident/retention operations for evidence and deployment boundaries. Cloudflare analytics is left unchanged and disclosed.
The daily security-retention workflow uses the existing protected production SUPABASE_DB_URL and removes audit entries older than 30 days, daily at 06:17 UTC. It is not a new runtime/build secret. npm run check:legal checks immutable policy snapshots; npm run licenses:generate creates dependency notices, also run automatically before development and production builds. The public /open-source-licenses page provides searchable, expandable notices; full texts load on demand from generated static files. /third-party-notices.txt remains the complete downloadable bundle. All generated catalog/notice files are ignored by Git and copied with public into the production image.
| Capability | Description |
|---|---|
| 💼 Track multiple jobs | Keep job-specific colors, pay rates, deductions, and weekly caps organized in one workspace. |
| 📅 Plan shifts ahead | Add upcoming work and see it included in projected weekly hours. |
| Get clear warnings at 80%, 90%, and 100% of global or per-job weekly caps. | |
| 💰 Understand earnings | Gross pay, tax, deductions, and net—filtered by this week, this month, the last 3 or 6 months, year to date, all time, or a custom span of months. |
| 🧾 Keep history honest | A shift remembers the rate it was worked at, so a later raise never silently reprices work you already did. |
| 🗓️ See it on a calendar | Week and month views of your shifts. Overnight shifts appear on both days and clashes are marked. |
| 🔗 Connect Google Calendar | Optional and read-only: show your Google events beside your shifts and get a warning when a new shift clashes with one. A job can also opt in to adding shifts automatically from matching Google events. Nothing is ever written to Google. |
| 🔒 Use secure sign-in | Sign in with email, Google, or GitHub through Supabase Auth. Signed out, the site's home page is a public landing page with an interactive example week. |
| 📱 Work comfortably anywhere | Responsive dashboard, mobile navigation, keyboard-accessible controls, light or dark themes, and an installable app with an offline fallback page. |
| 🎭 Take a break | Imposter, a small party word game for friends, with QR-code and link invites. |
flowchart LR
Browser[Browser] --> Next[Next.js App Router]
Next --> Actions[Server actions and route handlers]
Browser --> Auth[Supabase Auth]
Actions --> Database[Supabase Postgres with RLS]
Auth --> Database
Admin[Server-only admin operations] --> Prisma[Prisma]
Admin --> SecretClient[Supabase secret-key client]
Prisma --> Database
SecretClient --> Database
Actions -. optional, read-only .-> Google[Google Calendar API]
Note: Browser-facing features use Supabase's publishable key and Row Level Security. Prisma and the secret-key client are server-only administrative tools; neither belongs in a browser bundle.
- Rules enforced twice. Weekly caps, earnings snapshots, the tax-plus-deductions ceiling, and a no-overlapping-shifts constraint are enforced in TypeScript and independently by Postgres triggers—so a direct authenticated write that skips the app is rejected too.
- Row Level Security everywhere. Every user-facing read and write goes through a cookie-bound publishable-key client; the secret key never reaches the browser.
- Strict CSP. A per-request nonce with
'strict-dynamic'and no'unsafe-inline'for scripts. No external script, style, image, or font hosts—fonts are bundled, not fetched. - Disabled means disabled. Disabling an account is enforced by database policies, so a session token issued before the ban cannot keep writing.
- Google access is read-only. Calendar tokens are stored encrypted and every Google call is made server-to-server. Event titles and details are never stored. The only thing kept is the start and end times of events a job has opted in to sync, which become shifts.
- Guarded pipeline. Gitleaks secret scanning, CodeQL, a production dependency audit, and a Docker build check all gate
mainbefore a release is built. Every high or critical production advisory blocks release; malformed audit reports fail closed. Dependabot alerts and security update PRs are enabled alongside the weekly audit and CodeQL runs.
The production audit currently reports no advisories. Prisma's configuration dependency is pinned to patched deepmerge-ts 8.0.2 through a scoped override, verified with Prisma generation, validation, and a production build. The full development audit still reports five affected packages from one unpatched braces advisory (GHSA-vfj7-8cjw-p6xm) in Next.js's ESLint dependency chain. This development dependency is excluded from the production audit; it is not suppressed in the full audit.
src/
├── app/ App Router pages, actions, callbacks, integrations, and health route
├── components/ Dashboard, calendar, game, landing, navigation, and UI primitives
├── lib/ Auth, Supabase, Google, validation, calculations, and types
prisma/ Prisma schema
public/welcome/ Pre-rendered landing art and dashboard captures
scripts/ SQL test runner, audit gate, and the landing-art renderer
supabase/migrations/ Immutable timestamped production SQL migrations
supabase/tests/ SQL suites for the triggers, policies, and grants
docs/screenshots/ Images used by this README
.github/workflows/ CI, scheduled audit, and scheduled CodeQL workflows
- Node.js 24 or a compatible runtime supported by Next.js 16
- A Supabase project with email, Google, and GitHub sign-in enabled
- Docker (only for the SQL tests or for testing the production image locally)
git clone https://github.com/pokharelsandeep333-commits/ShiftSentry.git
cd ShiftSentry
npm install
Copy-Item .env.example .env.local
Fill in the five Supabase and admin placeholders in .env.local. The two NEXT_PUBLIC_SUPABASE_* values are safe for the browser; SUPABASE_SECRET_KEY, DATABASE_URL, and ADMIN_EMAIL_ALLOWLIST are server-only. The four GOOGLE_* values are optional and server-only: they enable the read-only Google Calendar connection, which stays off unless GOOGLE_CALENDAR_ENABLED=true. Never commit the file.
You must apply the database schema to your Supabase project before running the app.
npx supabase login
npx supabase link --project-ref <your-project-ref>
npx supabase db push
Set the Supabase Auth Site URL to http://localhost:3000 and add the local callback to the Redirect URL allow list:
http://localhost:3000/auth/callback
Enable Email, Google, and GitHub under Authentication → Sign In / Providers. Each OAuth provider points back to Supabase's provider callback, https://<project-ref>.supabase.co/auth/v1/callback; Supabase then redirects to this app's allowed callback.
The optional Google Calendar connection is a separate Google OAuth client, not the Supabase Google provider. Register http://localhost:3000/integrations/google/callback as its authorized redirect URI, and generate GOOGLE_TOKEN_ENCRYPTION_KEY as 32 random bytes in base64 (the command is in .env.example).
npm run db:generate
npm run test:earnings
npm run test:auth
npm run test:game
npm run test:security
npm run test:sql
npx tsc --noEmit
npm run lint
npm run audit:production
npm run build
npm run dev
Open http://localhost:3000.
These are the same checks the pipeline runs on every pull request:
Merging a green PR into protected main deploys it. GitHub Actions builds an immutable sha-<commit> image, verifies and applies pending Supabase migrations, then promotes the same image to stable. EC2 Watchtower polls every 300 seconds. After the release job succeeds, verify the live health endpoint from an allowed external network until it returns status: "ok" and the exact merged commit. Allow up to ten minutes after promotion for the image pull and restart. GitHub-hosted runner requests to this endpoint receive HTTP 403, so public health verification is an operator step. A green release job confirms image promotion; the health response confirms the running deployment.
Live configuration remains in /home/ubuntu/sandeepcloud/docker-compose.yml and /home/ubuntu/sandeepcloud/nginx.conf. Runtime credentials are interpolated from /home/ubuntu/sandeepcloud/.env, owned by root:root with mode 600; run Compose through sudo so it can read that file. Watchtower's Docker Hub credentials remain in /home/ubuntu/sandeepcloud/watchtower/config/config.json, also root:root with mode 600. Nginx is the public ingress; the app has no public port mapping.
Tip
Without Supabase configuration the root route intentionally renders a read-only dashboard preview with sample data, so the app boots and is browsable before you have any credentials. With Supabase configured, a signed-out visit to / shows the public landing page instead, at the same address.




