Conversation
| } | ||
|
|
||
| export function EmailForm({ onNext }: Props) { | ||
| const [email, setEmail] = useState(""); |
There was a problem hiding this comment.
lets install react hook form and zod (or other packages if you have better ones) for this to avoid having useStates cause that gets out of hand very quickly
There was a problem hiding this comment.
we can integrate with shadcn here too. They seem to support both react hook form and tanstack form so take your pick! https://ui.shadcn.com/docs/forms
| </button> | ||
| </form> | ||
| ); | ||
| } No newline at end of file |
There was a problem hiding this comment.
new line end of file please
| onChange={(e) => setEmail(e.target.value)} | ||
| required | ||
| /> | ||
| <button |
| color: var(--foreground); | ||
| font-family: Arial, Helvetica, sans-serif; | ||
| } | ||
|
|
There was a problem hiding this comment.
Could you set up a tailwind config and colors file? Guide: https://tailwindcss.com/docs/installation/framework-guides/nextjs
There was a problem hiding this comment.
All of our colors for the application should be defined in this file (e.g. primary, secondary, accent, etc) and then referenced by those names throughout all files
| </div> | ||
| </main> | ||
| ); | ||
| } No newline at end of file |
There was a problem hiding this comment.
Please add newlines at the ends of all files
|
|
||
| async function createUserProject(userId: string, projectId: string) { | ||
| const existing = await prisma.userProject.findUnique({ | ||
| where: { userId_projectId: { userId, projectId } } |
| } | ||
|
|
||
| async function getUserProject(userId: string, projectId: string) { | ||
| return await prisma.userProject.findUnique({ |
There was a problem hiding this comment.
also move inline, anything this short can be inline
| }); | ||
| } | ||
|
|
||
| async function getUserProjects(filters?: { userId?: string, projectId?: string }) { |
There was a problem hiding this comment.
for simplicity, lets destructure the args in the function definition so go { userId?, projectId? } : { userId: ..., projectId: string } or however it goes
| } | ||
|
|
||
| async function deleteUserProject(userId: string, projectId: string) { | ||
| return await prisma.userProject.delete({ |
There was a problem hiding this comment.
do we have soft deletes for this or not on this one?
| }); | ||
| } | ||
|
|
||
| async function deleteUserProject(userId: string, projectId: string) { |
There was a problem hiding this comment.
this might be better off being named removeUserFromProject
There was a problem hiding this comment.
Review written by a Claude agent.
Quick note before the rest: none of the four functions in this file are exported yet, so as merged today this genuinely is inert — nothing can call it. That's consistent with a "land the data layer now, wire up an authenticated action layer in a follow-up" plan, which seems like a reasonable way to sequence this. The comments below are mostly about what needs to be true by the time export gets added (which the PR title suggests is coming soon), plus one thing that blocks the build regardless.
Must-fix regardless of sequencing:
- This doesn't currently build —
tscfails with threeTS2353errors fromdeletedAtbeing nested inside the compound-keywhereclause (see inline comment oncreateUserProject; same pattern repeats ingetUserProjectandremoveUserFromProject). Worth fixing before merge independent of anything else here. - The upsert in
createUserProjectsilently un-revokes a soft-deleted membership with no audit trail (see inline comment). - No input validation —
zodis already a dependency elsewhere in the repo. prettier --checkcurrently fails on this file (brace spacing, trailing whitespace) — this repo's CLAUDE.md treatsformat:checkas a merge gate.
Pending authz ticket / for whenever export gets added (see note above):
createUserProject/removeUserFromProjectwill need a caller-identity + authority check the moment they're exported, since this table is the authorization-scoping mechanism for the whole multi-tenant system (see inline comment).getUserProjects({})currently has no scoping and would return every membership across every project once reachable.
Worth its own ticket, not introduced by this PR: same RLS gap flagged on the other open PRs — UserProject sits in the same unprotected schema, which matters even more here since this table is the actual access-control list.
(Edit: corrected the inline comment anchors below that were off in my first pass — same content, right lines now.)
| import { prisma } from "@/lib/prisma"; | ||
|
|
||
| async function createUserProject(userId: string, projectId: string) { | ||
| const existing = await prisma.userProject.findUnique({ where: { userId_projectId: { userId, projectId, deletedAt: null } }}); |
There was a problem hiding this comment.
deletedAt: null is nested inside the compound-key object here, but Prisma's generated UserProjectUserIdProjectIdCompoundUniqueInput type only has userId/projectId — running tsc --noEmit against this branch fails here (and at the matching spots in getUserProject/removeUserFromProject) with TS2353: Object literal may only specify known properties. This one's independent of anything else in the review — npm run build won't pass as-is. The fix is to move deletedAt: null up a level, alongside the compound key (where: { userId_projectId: { userId, projectId }, deletedAt: null }), not to just delete it — deleting it would make a soft-deleted membership permanently block re-adding that user (see the upsert note below).
| if (existing) { | ||
| throw new Error("User project already exists"); | ||
| } | ||
| return await prisma.userProject.upsert({ |
There was a problem hiding this comment.
Once the type error above is fixed, this upsert will silently resurrect a previously soft-deleted membership (update: { deletedAt: null }) with no change to createdAt and no audit trail that access was ever removed and re-granted. Worth setting a fresh timestamp (or a dedicated restoredAt field) on the update branch so a re-grant is visible later, independent of whatever authorization ends up wrapping this function.
| "use server"; | ||
| import { prisma } from "@/lib/prisma"; | ||
|
|
||
| async function createUserProject(userId: string, projectId: string) { |
There was a problem hiding this comment.
Noticed none of the four functions in this file are actually exported yet, so as merged this is dead code and not reachable — which reads like this might genuinely be the "data layer first, action layer later" pattern. If that's the plan, just want to flag it explicitly: whenever export gets added here (which the PR title implies is coming), it'll need a caller-identity + authority check attached at the same time, since UserProject is the authorization-scoping table for the whole system — an unauthenticated grant/revoke here is more consequential than on the other open PRs. Also worth scoping getUserProjects (line 22) to the caller once it's exposed — an empty {} filter currently returns every membership across all projects.
Created lib/userProject.ts for managing the UserProject join table which contains: