Commit ee5bdfb
fix(billing): reconcile the admin course actions and unblock re-join (#550)
Both found by driving the flows in a browser rather than by reading code.
`archiveCourse` exists twice. `app/actions/teacher/courses.ts` backs the
teacher course card; `app/actions/admin/courses.ts` backs the admin
course-status screen, and that is the one the admin UI actually calls. Only
the first was reconciling, so archiving from the admin screen — the more
likely path for an admin racing a cutoff — left the cutoff in place and the
recovery loop open on exactly the half a reviewer would try first.
`restoreCourse` is the mirror image: it raises the active course count, so it
now reconciles too and schedules the cutoff at the moment the admin causes it,
with the full grace period, instead of whenever a sweep next notices.
The `/join-school` page gated on a `tenant_users` row existing at all, so a
removed member — redirected there by proxy.ts precisely so they could re-join —
was met with "You're Already a Member!" and no way forward. The server action
already handled `status`; the page did not. Both membership reads on that page
now require `status = 'active'`, matching `joinCurrentSchool()`.
Verified end to end against local Supabase, no cron running: admin-screen
archive clears `access_cutoff_at` synchronously and the student's
/access-suspended page redirects them back into the course; Remove from School
clears it on the student limit; re-joining while at the limit is still refused
with "This school has reached its student limit", and re-joining under it
updates the surviving row in place (one row, `active`) rather than inserting a
duplicate. Re-check limits reports "Still over the plan limit" while over and
"Access restored" once under.
Also fixes six pre-existing lint errors in the join-school page, which the
whole-file pre-commit hook makes blocking as soon as the file is touched: five
unescaped apostrophes, and an `any` on the memberships map. Removing that `any`
surfaced a real mismatch — the generated types model the `tenants` embed as an
array while PostgREST returns a bare object — so the row now handles both
shapes instead of casting the discrepancy away.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015V35rmrQiZCxDJDiXas9GV1 parent a126e98 commit ee5bdfb
2 files changed
Lines changed: 45 additions & 12 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
34 | 34 | | |
35 | 35 | | |
36 | 36 | | |
37 | | - | |
| 37 | + | |
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
| |||
47 | 47 | | |
48 | 48 | | |
49 | 49 | | |
50 | | - | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
51 | 56 | | |
52 | 57 | | |
53 | 58 | | |
54 | 59 | | |
55 | 60 | | |
56 | | - | |
| 61 | + | |
| 62 | + | |
57 | 63 | | |
58 | 64 | | |
59 | 65 | | |
| |||
62 | 68 | | |
63 | 69 | | |
64 | 70 | | |
65 | | - | |
| 71 | + | |
66 | 72 | | |
67 | 73 | | |
68 | | - | |
| 74 | + | |
69 | 75 | | |
70 | 76 | | |
71 | 77 | | |
| |||
86 | 92 | | |
87 | 93 | | |
88 | 94 | | |
89 | | - | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
90 | 98 | | |
91 | 99 | | |
92 | 100 | | |
93 | 101 | | |
| 102 | + | |
94 | 103 | | |
95 | 104 | | |
96 | 105 | | |
| |||
111 | 120 | | |
112 | 121 | | |
113 | 122 | | |
114 | | - | |
| 123 | + | |
115 | 124 | | |
116 | 125 | | |
117 | 126 | | |
118 | 127 | | |
119 | | - | |
120 | | - | |
121 | | - | |
122 | | - | |
123 | | - | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
124 | 142 | | |
125 | 143 | | |
126 | 144 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| 7 | + | |
7 | 8 | | |
8 | 9 | | |
9 | 10 | | |
| |||
132 | 133 | | |
133 | 134 | | |
134 | 135 | | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
135 | 142 | | |
136 | 143 | | |
137 | 144 | | |
| 145 | + | |
138 | 146 | | |
139 | 147 | | |
140 | 148 | | |
| |||
199 | 207 | | |
200 | 208 | | |
201 | 209 | | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
202 | 216 | | |
203 | 217 | | |
204 | 218 | | |
| 219 | + | |
205 | 220 | | |
206 | 221 | | |
207 | 222 | | |
| |||
0 commit comments