You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
-`/books/search` — search Google Books + add to shelves/clubs
341
360
-`/books/[googleVolumeId]` — book details (from DB or fetched+cached; store book data in our DB after any user searches for it or tries to add it to a club/shelf)
-derived status (`ACTIVE`, `INACTIVE`, `EXPIRED`, `EXHAUSTED`)
426
445
- usage count
427
446
- optional expiry
428
447
- optional max uses
429
448
- creator
430
449
- Create new code with:
431
-
- purpose
450
+
- purpose (`BETA_SIGNUP` only in Milestone 5 UI)
432
451
- label
433
452
- optional expiry
434
453
- optional max uses
435
-
-Show the raw code once after creation.
454
+
-Generate the raw code server-side and show it once after creation.
436
455
- Allow activation/deactivation.
456
+
- Do not support editing the raw code, expiry, or max uses after creation in Milestone 5.
437
457
- Show redemption history or usage details sufficient to explain exhausted or inactive state.
438
458
439
459
Club Home (`/clubs/[clubId]`)
@@ -542,15 +562,17 @@ Navigation
542
562
- required at signup completion
543
563
- must match an active `BETA_SIGNUP` code
544
564
- must respect expiry and max-uses rules when those are configured
565
+
- successful redemption is counted only once per user
545
566
- Internal admin sign-in:
546
567
- valid email required
547
568
- password required
548
-
- credentials verified against stored password hash
569
+
- credentials verified against stored bcrypt-compatible password hash
549
570
- Invitation code creation:
550
571
-`purpose` required
551
572
-`label` required
552
573
-`expiresAt` optional and, if present, must be a future timestamp
553
574
-`maxUses` optional and, if present, must be a positive integer
575
+
- raw code value is generated by the server
554
576
- Club name: 2–60 chars
555
577
- Thread title: 2–120 chars
556
578
- Post body: 1–10,000 chars
@@ -592,6 +614,7 @@ Navigation
592
614
- for shelves, always resolve ownership internally by userId after nickname lookup
593
615
- Provider email must not be used as the authoritative public identity for authorization or invite acceptance.
594
616
- Public users cannot access `/admin/*`, and internal admins cannot use the public onboarding path.
617
+
- Deactivating or expiring an invitation code does not revoke access for users who already redeemed it successfully.
595
618
596
619
## 13) MVP Milestones
597
620
@@ -685,8 +708,17 @@ Use `sortOrder`:
685
708
- Internal admins use:
686
709
-`provider = 'internal'`
687
710
-`providerUserId = normalized email`
688
-
- password-hash verification
711
+
- bcrypt-compatible password-hash verification
712
+
- Manual Supabase bootstrap should populate:
713
+
-`provider`
714
+
-`provider_user_id`
715
+
-`email`
716
+
-`password_hash`
717
+
- optional `name`
718
+
- Internal admin bootstrap does not require inserting an `auth_accounts` row.
689
719
- Internal admins do not use public signup or public product routes in Milestone 5.
690
720
- Invitation codes are modeled for future reuse through `purpose`, optional expiry, and optional max uses, but only `BETA_SIGNUP` is redeemed in Milestone 5.
691
-
- Raw invitation codes are shown once at creation and then only the hash remains persisted.
721
+
- Raw invitation codes are system-generated, shown once at creation, and then only the hash remains persisted.
722
+
-`maxUses = null` means unlimited use; `expiresAt = null` means no expiry.
723
+
- Admins can activate/deactivate codes, but editing code body, expiry, or max uses after creation is out of scope in Milestone 5.
692
724
- Milestone 5 private club invites can only target existing signed-up public users with a nickname.
Copy file name to clipboardExpand all lines: docs/tasks/milestone-5/milestone-5-beta-onboarding-admin-auth-and-invitation-codes.md
+4Lines changed: 4 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -22,9 +22,12 @@ Deliver the closed-beta onboarding and service-native identity workflow on top o
22
22
- Internal admins are manually created in Supabase UI and stored with `provider = 'internal'`.
23
23
- Internal admins use email/password only and do not participate in public signup or the public product routes.
24
24
- Invitation codes are future-ready through `purpose`, optional expiry, and optional max uses, but only `BETA_SIGNUP` redemption is implemented in Milestone 5.
25
+
- Invitation codes are system-generated by the app and shown in raw form only once at creation time; admins do not type custom raw code values in Milestone 5.
26
+
- Invitation-code status in the admin UI is derived from `isActive`, `expiresAt`, and successful redemption count so admins can distinguish active, inactive, expired, and exhausted codes.
25
27
- Nickname is immutable in Milestone 5 and is validated as a lowercase URL-safe handle.
26
28
- Private club invites remain a separate domain from admin-managed invitation codes.
27
29
- Milestone 5 does not add a public profile route, nickname change UI, or in-app admin-user bootstrap flow.
30
+
- Signed-in public users who try to access `/admin/*` should see a forbidden experience, while internal admins who try to enter `/signup` or reader-app routes should be redirected back to `/admin/invitation-codes`.
28
31
29
32
## Delivery Order
30
33
1.[Task 01: User and Admin Identity Foundation](./task-01-user-and-admin-identity-foundation.md)
@@ -38,6 +41,7 @@ Deliver the closed-beta onboarding and service-native identity workflow on top o
38
41
- Completing signup persists nickname, gender, country, favorite genres, and signup completion state, and atomically redeems a valid `BETA_SIGNUP` invitation code.
39
42
- Internal admins can sign in through `/admin/signin` and manage invitation codes from `/admin/invitation-codes`.
40
43
- Invitation codes are stored hashed, support active/inactive state, and can optionally expire or cap uses.
44
+
- Manual Supabase bootstrap requirements for internal admins are documented clearly enough that an operator can create an internal admin without app-side seeding.
41
45
- Nickname becomes the default user-facing identity across `/me`, shelves, clubs, threads, reviews, and invite pages.
42
46
- Public shelf sharing works by nickname route while preserving the existing signed-in-only public shelf access model.
43
47
- Private club invites are created by nickname and accepted only by the targeted signed-in public user.
Copy file name to clipboardExpand all lines: docs/tasks/milestone-5/task-02-signup-completion-and-public-app-auth-gating.md
+4Lines changed: 4 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -19,6 +19,7 @@ Implement the completed-signup flow so authenticated but incomplete public users
19
19
- direct protected-page hits by incomplete public users
20
20
- Redirect incomplete public users away from reader-facing protected routes and protected mutations until `signup_completed_at` is set.
21
21
- Redirect completed public users away from `/signup` to the callback destination or `/books/search`.
22
+
- Ensure internal admins never enter the public signup flow and are redirected to `/admin/invitation-codes` instead.
22
23
23
24
## Implementation Notes
24
25
- Keep Google OAuth as the only public-user provider for Milestone 5, but treat OAuth and completed signup as separate lifecycle steps.
@@ -28,13 +29,15 @@ Implement the completed-signup flow so authenticated but incomplete public users
28
29
- Invite links and other protected deep links should survive the onboarding detour by preserving callback intent.
29
30
- Sign-out remains available to incomplete public users.
30
31
- Invitation-code redemption should record audit history and enforce inactive, expired, exhausted, and wrong-purpose rejections on the server.
32
+
- A successful signup redemption should consume one available use exactly once, even under concurrent submission attempts.
31
33
32
34
## Acceptance Criteria
33
35
- A newly authenticated but incomplete public user is redirected to `/signup` before any reader-facing app surface renders.
34
36
- Completing signup writes the required profile fields, marks signup complete, redeems the code, and redirects the user back to the intended destination.
35
37
- Protected reader-facing server actions reject incomplete public users consistently.
36
38
- Completed public users cannot accidentally return to `/signup` as a normal app page.
37
39
- Invalid, inactive, expired, exhausted, and wrong-purpose invitation codes are rejected with clear server-enforced behavior.
40
+
- Internal admins cannot complete public signup and are redirected back to the admin panel instead.
38
41
39
42
## Expected Touchpoints
40
43
-`app/signup/page.tsx`
@@ -43,4 +46,5 @@ Implement the completed-signup flow so authenticated but incomplete public users
Copy file name to clipboardExpand all lines: docs/tasks/milestone-5/task-03-internal-admin-auth-and-invitation-code-management.md
+10-1Lines changed: 10 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -5,11 +5,12 @@ Add the internal-only auth path and admin surface needed to manage invitation co
5
5
6
6
## Scope
7
7
- Add `/admin/signin` for internal admin email/password login.
8
+
- Add `/admin` as a lightweight landing route that redirects to `/admin/invitation-codes`.
8
9
- Configure Auth.js Credentials auth for internal users stored with `provider = 'internal'`.
9
10
- Add internal-only route protection for `/admin/*`.
10
11
- Add `/admin/invitation-codes` with the required Milestone 5 flows:
11
12
- list codes with purpose, label, status, usage count, expiry, max uses, and creator
12
-
- create a code with purpose, label, optional expiry, and optional max uses
13
+
- create a system-generated code with purpose, label, optional expiry, and optional max uses
13
14
- activate/deactivate a code
14
15
- show usage details or redemption history sufficient to explain inactive or exhausted state
15
16
- Show the raw invitation code only once at creation time while persisting only the hash.
@@ -21,18 +22,26 @@ Add the internal-only auth path and admin surface needed to manage invitation co
21
22
- Internal users should land on `/admin/invitation-codes` after successful sign-in.
22
23
- Invitation-code management is future-ready through `purpose`, optional expiry, and optional max uses, but only `BETA_SIGNUP` redemption is implemented in Milestone 5.
23
24
- The admin route contract can stay compact; usage details may be inline on `/admin/invitation-codes` instead of requiring a second admin detail route.
25
+
- In Milestone 5, code creation UI can expose `purpose` as a fixed single-option control or a read-only value so implementation does not need a multi-purpose admin UX yet.
26
+
- Admins can activate/deactivate codes, but editing a code’s raw value, expiry, or max uses after creation is out of scope in Milestone 5.
27
+
- The list surface should show derived status, not just raw `isActive`, so exhausted and expired codes are immediately understandable.
24
28
25
29
## Acceptance Criteria
26
30
- Internal admins can authenticate successfully with email/password through `/admin/signin`.
27
31
- Invalid credentials fail cleanly without creating or mutating users.
28
32
-`/admin/invitation-codes` lets internal admins create, activate/deactivate, and inspect codes and usage data.
29
33
- Raw codes are only shown at creation time and are not persisted in plaintext.
30
34
- Public users are blocked from `/admin/*`, and internal admins are redirected away from `/signup`.
35
+
- Manual internal-admin bootstrap is documented with enough field-level detail to be reproducible through Supabase UI.
31
36
32
37
## Expected Touchpoints
33
38
-`app/admin/signin/page.tsx`
39
+
-`app/admin/page.tsx`
40
+
-`app/admin/layout.tsx`
34
41
-`app/admin/invitation-codes/page.tsx`
35
42
-`app/api/auth/[...nextauth]/route.ts`
43
+
-`components/admin/*`
36
44
-`proxy.ts`
37
45
-`lib/auth/*`
46
+
-`lib/invitation-codes/*`
38
47
- invitation-code repositories, server actions, and tests
Copy file name to clipboardExpand all lines: docs/tasks/milestone-5/task-04-nickname-profile-public-shelf-sharing-and-club-invites.md
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -29,6 +29,7 @@ Make nickname the default reader-facing identity, switch public shelf sharing fr
29
29
- Invitation codes and private club invites are separate domains and should not be coupled in repository or UI behavior.
30
30
- Inviting someone who has not completed signup is out of scope; the club-invite flow should fail clearly when the nickname does not map to a signed-up public user.
31
31
- Duplicate pending club invites should be blocked per `clubId + invitedUserId`.
32
+
- Invite acceptance UI and copy should stop referring to email entirely and should identify the invite target by nickname or a generic targeted-user message.
32
33
33
34
## Acceptance Criteria
34
35
-`/me` clearly shows nickname as the primary Book by Book identity and surfaces the new profile fields.
@@ -37,6 +38,7 @@ Make nickname the default reader-facing identity, switch public shelf sharing fr
37
38
- Club admins create private invites by nickname, not email.
38
39
- Private-club invite creation fails with a clear error when the nickname does not exist, is incomplete, or already belongs to a club member.
39
40
- Accepting a valid private-club invite works only for the targeted signed-in public user and remains idempotent.
41
+
- Reader-facing invite pages and member/profile surfaces no longer rely on provider email as the visible identity fallback when nickname exists.
0 commit comments