E-Sign Contract Page — Playground Prototype + SvelteKit Repo #72

Open
opened 2026-03-24 00:39:12 +00:00 by forgejo_admin · 2 comments

Type

Feature

Lineage

plan-wkq → Phase 14 (Billing Tiers & Contracts) → E-Sign Contract Page

Repo

forgejo_admin/westside-app (issue home), new repo westside-contracts created as part of this work. Prototype in pal-e-playground.

User Story

As a parent
I want to receive an email with a link to a digital contract, read the season terms, and sign with a drawn signature
So that I can officially commit to the program without paper forms and the org has a legally traceable record

Context

Westside Kings & Queens needs digital contracts for the spring/summer 2026 season. Two contract types: Travel teams (17U Elite, 17U Select, 16U Elite) with monthly fees + tournament travel costs, and Local teams with monthly fees + group-decided tournaments.

The signing flow: parent receives a tokenized email link → contract page renders terms for their player's team → checkbox unlocks a drawn signature pad (phone finger / desktop mouse) → signature + timestamp + IP stored in DB, image in MinIO. Under-18 players require parent/guardian signature, enforced by token→parent association.

E-SIGN Act compliant: intent (checkbox), consent (clicked link), association (token→email), retention (DB + MinIO).

The new westside-contracts repo uses SvelteKit with adapter-node so server routes can talk directly to basketball-api's Postgres — no separate backend needed. HTML/CSS prototype in pal-e-playground is the literal source of truth for the SvelteKit components.

Travel contract includes: $200/month (or tier), 2 practices/week + optional weight room, jersey, Power 32 Circuit registration ($186/player), travel costs per trip (Seal Beach $395, Vegas TBD, Championship TBD), practice schedule, Vertical Raise fundraising info, Klarna installments.

Local contract includes: $200/month, 2 practices/week, jersey, practice schedule, tournaments decided as a group via GroupMe, separate bill when confirmed.

File Targets

Phase 1 — Playground:

  • pal-e-playground/westside-contract.html — single-file prototype (HTML/CSS/JS)

Phase 2 — SvelteKit repo (new: westside-contracts):

  • src/routes/contract/[token]/+page.svelte — contract page (from playground HTML)
  • src/routes/contract/[token]/+page.server.ts — token resolution, Postgres query
  • src/routes/contract/[token]/sign/+server.ts — POST endpoint for signature submission
  • src/lib/db.ts — Postgres connection (direct to basketball-api DB)
  • src/lib/minio.ts — signature image upload
  • Dockerfile — adapter-node production build
  • svelte.config.js — adapter-node config

Phase 3 — Email:

  • MJML template for contract invitation email (via pal-e-mail or Gmail MCP)

basketball-api (migration only):

  • New alembic migration: add contract_signature_url column to players table

Files NOT to touch:

  • westside-app/ — existing SPA, separate concern
  • basketball-api/src/ — no route changes, migration only

Acceptance Criteria

  • Playground prototype approved by Lucas on mobile
  • SvelteKit repo deployed and accessible via Tailscale funnel
  • Token-based contract link resolves to correct player/parent
  • Drawn signature captured and stored (MinIO image + DB record)
  • Under-18 enforced: contract page shows "Parent/Guardian Signature Required"
  • Marcus approves final email content and contract terms
  • Demo email sent to Marcus → clicks link → signs contract successfully
  • Signed contract record verified in database (status, timestamp, IP, signature URL)
  • DONE = a real email is sent, accepted, and signed — full flow proven end to end

Test Expectations

  • Token resolution: valid token returns player + parent data, expired/invalid returns 404
  • Signature submit: POST with signature image returns 200, stores to MinIO, updates DB
  • Re-sign prevention: already-signed contract returns 409
  • Under-18 gate: contract page renders parent language when player DOB < 18
  • Run command: npm run test (SvelteKit) + pytest tests/test_contract.py (migration verification)

Constraints

  • signature_pad.js via CDN (no npm package needed for playground, npm install for SvelteKit)
  • Westside branding: black (#0a0a0a), red (#d42026), white (#ffffff) — match existing brand_wrapper colors
  • Mobile-first: signature pad must work with touch input on 390px viewport
  • SvelteKit adapter-node (NOT adapter-static) — needs server routes
  • Direct Postgres connection from SvelteKit (same DB as basketball-api, read player/parent/team data, write contract fields)
  • Kustomize deployment pattern matching existing overlays in pal-e-deployments

Checklist

  • Playground HTML approved on mobile
  • New repo created on Forgejo
  • CI pipeline configured (Woodpecker)
  • Kustomize overlay added to pal-e-deployments
  • Alembic migration for contract_signature_url
  • PR opened
  • Tests pass
  • No unrelated changes
  • project-westside-basketball — project page
  • plan-wkq → Phase 14 — Billing Tiers & Contracts
  • westside-practice-schedule — practice schedule data for contract terms
  • tourney-power32-seal-beach-may-2026 — tournament cost data
  • westside-vertical-raise-fundraising — fundraising details
  • westside-tournament-schedule — parent tournament schedule note
### Type Feature ### Lineage `plan-wkq` → Phase 14 (Billing Tiers & Contracts) → E-Sign Contract Page ### Repo `forgejo_admin/westside-app` (issue home), new repo `westside-contracts` created as part of this work. Prototype in `pal-e-playground`. ### User Story As a parent I want to receive an email with a link to a digital contract, read the season terms, and sign with a drawn signature So that I can officially commit to the program without paper forms and the org has a legally traceable record ### Context Westside Kings & Queens needs digital contracts for the spring/summer 2026 season. Two contract types: Travel teams (17U Elite, 17U Select, 16U Elite) with monthly fees + tournament travel costs, and Local teams with monthly fees + group-decided tournaments. The signing flow: parent receives a tokenized email link → contract page renders terms for their player's team → checkbox unlocks a drawn signature pad (phone finger / desktop mouse) → signature + timestamp + IP stored in DB, image in MinIO. Under-18 players require parent/guardian signature, enforced by token→parent association. E-SIGN Act compliant: intent (checkbox), consent (clicked link), association (token→email), retention (DB + MinIO). The new `westside-contracts` repo uses SvelteKit with `adapter-node` so server routes can talk directly to basketball-api's Postgres — no separate backend needed. HTML/CSS prototype in pal-e-playground is the literal source of truth for the SvelteKit components. **Travel contract includes:** $200/month (or tier), 2 practices/week + optional weight room, jersey, Power 32 Circuit registration ($186/player), travel costs per trip (Seal Beach $395, Vegas TBD, Championship TBD), practice schedule, Vertical Raise fundraising info, Klarna installments. **Local contract includes:** $200/month, 2 practices/week, jersey, practice schedule, tournaments decided as a group via GroupMe, separate bill when confirmed. ### File Targets **Phase 1 — Playground:** - `pal-e-playground/westside-contract.html` — single-file prototype (HTML/CSS/JS) **Phase 2 — SvelteKit repo (new: westside-contracts):** - `src/routes/contract/[token]/+page.svelte` — contract page (from playground HTML) - `src/routes/contract/[token]/+page.server.ts` — token resolution, Postgres query - `src/routes/contract/[token]/sign/+server.ts` — POST endpoint for signature submission - `src/lib/db.ts` — Postgres connection (direct to basketball-api DB) - `src/lib/minio.ts` — signature image upload - `Dockerfile` — adapter-node production build - `svelte.config.js` — adapter-node config **Phase 3 — Email:** - MJML template for contract invitation email (via pal-e-mail or Gmail MCP) **basketball-api (migration only):** - New alembic migration: add `contract_signature_url` column to players table Files NOT to touch: - `westside-app/` — existing SPA, separate concern - `basketball-api/src/` — no route changes, migration only ### Acceptance Criteria - [ ] Playground prototype approved by Lucas on mobile - [ ] SvelteKit repo deployed and accessible via Tailscale funnel - [ ] Token-based contract link resolves to correct player/parent - [ ] Drawn signature captured and stored (MinIO image + DB record) - [ ] Under-18 enforced: contract page shows "Parent/Guardian Signature Required" - [ ] Marcus approves final email content and contract terms - [ ] Demo email sent to Marcus → clicks link → signs contract successfully - [ ] Signed contract record verified in database (status, timestamp, IP, signature URL) - [ ] **DONE = a real email is sent, accepted, and signed — full flow proven end to end** ### Test Expectations - [ ] Token resolution: valid token returns player + parent data, expired/invalid returns 404 - [ ] Signature submit: POST with signature image returns 200, stores to MinIO, updates DB - [ ] Re-sign prevention: already-signed contract returns 409 - [ ] Under-18 gate: contract page renders parent language when player DOB < 18 - Run command: `npm run test` (SvelteKit) + `pytest tests/test_contract.py` (migration verification) ### Constraints - `signature_pad.js` via CDN (no npm package needed for playground, npm install for SvelteKit) - Westside branding: black (#0a0a0a), red (#d42026), white (#ffffff) — match existing `brand_wrapper` colors - Mobile-first: signature pad must work with touch input on 390px viewport - SvelteKit `adapter-node` (NOT adapter-static) — needs server routes - Direct Postgres connection from SvelteKit (same DB as basketball-api, read player/parent/team data, write contract fields) - Kustomize deployment pattern matching existing overlays in pal-e-deployments ### Checklist - [ ] Playground HTML approved on mobile - [ ] New repo created on Forgejo - [ ] CI pipeline configured (Woodpecker) - [ ] Kustomize overlay added to pal-e-deployments - [ ] Alembic migration for contract_signature_url - [ ] PR opened - [ ] Tests pass - [ ] No unrelated changes ### Related - `project-westside-basketball` — project page - `plan-wkq` → Phase 14 — Billing Tiers & Contracts - `westside-practice-schedule` — practice schedule data for contract terms - `tourney-power32-seal-beach-may-2026` — tournament cost data - `westside-vertical-raise-fundraising` — fundraising details - `westside-tournament-schedule` — parent tournament schedule note
Author
Owner

QA Scope Review — Decisions Made

QA agent reviewed this ticket and identified 5 gaps. All resolved:

Token Design

Decision: secrets.token_urlsafe(32) — same pattern as Parent.registration_token and Coach.invite_token. NOT JWT. Stored as contract_token column on Player model. No expiry (one-time sign).

Prerequisite: basketball-api #152 — adds contract_token + contract_signature_url to Player model. Must merge first.

Contract Variant Routing

Decision: Derive from team name. "Local" in team.name → local contract template. Otherwise → travel contract template. No new columns or URL params needed.

Shared DB Access

Decision: westside-contracts connects to postgres.basketball-api.svc.cluster.local:5432 via cross-namespace DNS. Database credentials shared via k8s Secret. SQLAlchemy models duplicated (minimal subset: Player, Parent, Team — read-only except contract fields). Schema ownership stays with basketball-api (all migrations there).

Migration Ownership

Decision: basketball-api owns all migrations. Separate ticket: basketball-api #152 (2 points). Blocking dependency for Phase 2.

Contract Content Versioning

Decision: Add contract_version string to the signing record (e.g., "2026-spring-travel-v1"). Simple, sufficient for E-SIGN audit trail. Tracked in the same migration (#152).

Travel vs Local Contract Content

  • Travel teams (17U Elite, 17U Select, 16U Elite): $200/month, jersey, $186 circuit registration, trip costs (Seal Beach $395, Vegas TBD, Championship TBD), practice schedule, Vertical Raise fundraising, Klarna option. Lists all 3 circuit tournaments + Utah State Invitational.
  • Local teams (17U Local, 16U Local, Queens): $200/month, jersey, practice schedule. "Once signed, we will connect with all parents to discuss tournaments and leagues in the state of Utah. Communications via GroupMe. When we're ready to register, we'll send out a bill."

Deployment Details (still TBD — resolve before agent spawn)

  • Namespace: TBD
  • Tailscale hostname: TBD
  • Port: TBD (likely 3000 to match westside-app pattern)
  • Harbor project: TBD
## QA Scope Review — Decisions Made QA agent reviewed this ticket and identified 5 gaps. All resolved: ### Token Design **Decision:** `secrets.token_urlsafe(32)` — same pattern as `Parent.registration_token` and `Coach.invite_token`. NOT JWT. Stored as `contract_token` column on Player model. No expiry (one-time sign). **Prerequisite:** basketball-api #152 — adds `contract_token` + `contract_signature_url` to Player model. Must merge first. ### Contract Variant Routing **Decision:** Derive from team name. `"Local" in team.name` → local contract template. Otherwise → travel contract template. No new columns or URL params needed. ### Shared DB Access **Decision:** westside-contracts connects to `postgres.basketball-api.svc.cluster.local:5432` via cross-namespace DNS. Database credentials shared via k8s Secret. SQLAlchemy models duplicated (minimal subset: Player, Parent, Team — read-only except contract fields). Schema ownership stays with basketball-api (all migrations there). ### Migration Ownership **Decision:** basketball-api owns all migrations. Separate ticket: basketball-api #152 (2 points). Blocking dependency for Phase 2. ### Contract Content Versioning **Decision:** Add `contract_version` string to the signing record (e.g., "2026-spring-travel-v1"). Simple, sufficient for E-SIGN audit trail. Tracked in the same migration (#152). ### Travel vs Local Contract Content - **Travel teams (17U Elite, 17U Select, 16U Elite):** $200/month, jersey, $186 circuit registration, trip costs (Seal Beach $395, Vegas TBD, Championship TBD), practice schedule, Vertical Raise fundraising, Klarna option. Lists all 3 circuit tournaments + Utah State Invitational. - **Local teams (17U Local, 16U Local, Queens):** $200/month, jersey, practice schedule. "Once signed, we will connect with all parents to discuss tournaments and leagues in the state of Utah. Communications via GroupMe. When we're ready to register, we'll send out a bill." ### Deployment Details (still TBD — resolve before agent spawn) - Namespace: TBD - Tailscale hostname: TBD - Port: TBD (likely 3000 to match westside-app pattern) - Harbor project: TBD
Author
Owner

Additional AC — Confirmation Email

After a parent signs the contract, a confirmation email must be sent automatically:

  • "Thank you, [Player Name] is confirmed on [Team Name]"
  • GroupMe link for their team
  • Reminder of first payment date (April 6)
  • Practice schedule summary

This is part of the end-to-end flow: contract email → sign → confirmation email with GroupMe + next steps.

Additional Contract Sections Added

  • Split payments: $100 on the 15th, $100 on the 30th as a standing option
  • Communication: GroupMe link sent after signing
  • Rules of Conduct: no hazing (zero tolerance), respect lodging property, damages charged to player
  • Prorated first month: TBD — needs Lucas's decision on April amount
## Additional AC — Confirmation Email After a parent signs the contract, a confirmation email must be sent automatically: - "Thank you, [Player Name] is confirmed on [Team Name]" - GroupMe link for their team - Reminder of first payment date (April 6) - Practice schedule summary This is part of the end-to-end flow: contract email → sign → confirmation email with GroupMe + next steps. ## Additional Contract Sections Added - **Split payments**: $100 on the 15th, $100 on the 30th as a standing option - **Communication**: GroupMe link sent after signing - **Rules of Conduct**: no hazing (zero tolerance), respect lodging property, damages charged to player - **Prorated first month**: TBD — needs Lucas's decision on April amount
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
forgejo_admin/westside-landing#72
No description provided.