Feature/68 add edit user#75
Conversation
There was a problem hiding this comment.
Pull request overview
Adds admin-facing user management under /users, including creating new users (optionally with complimentary Stripe trial months) and editing existing users’ email/role, backed by new server actions and supporting UI dialogs.
Changes:
- Added admin server actions (
createUserAdmin,updateUserAdmin) with Zod validation and Supabase Admin API integration. - Added Stripe helper to grant complimentary subscription trials and updated user listing to treat
trialingas Active. - Implemented Create/Edit user dialogs and wired the table “edit” (pencil) action to open the edit flow.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| utils/supabase/admin.ts | Adds a Supabase Admin client factory for server-side admin operations. |
| lib/stripe.ts | Adds complimentary subscription grant helper and sync step. |
| components/ui/alert-dialog.tsx | Introduces shadcn-style AlertDialog component for confirmation modals. |
| app/(authenticated)/users/schema.ts | Adds Zod schemas for create/update admin user actions. |
| app/(authenticated)/users/queries.ts | Treats trialing subscription status as Active in the users list. |
| app/(authenticated)/users/actions.ts | Implements admin create/update user server actions (Supabase + Drizzle + Stripe trial grant). |
| app/(authenticated)/users/_components/users-columns.tsx | Refactors columns into a factory and wires edit button click handling. |
| app/(authenticated)/users/_components/users-client.tsx | Adds Create/Edit dialogs and connects edit action state to the table. |
| app/(authenticated)/users/_components/user-admin-dialog.tsx | Implements the Create User and Edit User dialog UIs with confirmation prompts. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const admin = createAdminClient(); | ||
| const { error: authError } = await admin.auth.admin.updateUserById( | ||
| user_id, | ||
| { email, app_metadata: { user_role: role } }, |
| updatedAt: new Date(), | ||
| }, | ||
| }); | ||
| } catch { |
| } catch { | ||
| return { | ||
| errors: { | ||
| _form: [ | ||
| "User was created, but the complimentary subscription could not be added. Please try again or contact support.", | ||
| ], | ||
| }, |
| limit: 1, | ||
| }); | ||
|
|
||
| const status = existing.data[0]?.status; | ||
| if (status === "active" || status === "trialing") { |
martin0024
left a comment
There was a problem hiding this comment.
solid work berny! happy to see a good frontend PR
| return ( | ||
| <Dialog open={open} onOpenChange={handleOpenChange}> | ||
| <DialogTrigger asChild> | ||
| <Button className="shrink-0 border-[#0040A1] bg-[#0040A1] text-white hover:bg-[#003380] hover:text-white"> |
There was a problem hiding this comment.
The button override the current theme, please avoid styling buttons inline, if you want different colors feel free to use variants: <Button variant='default/ghost... />
| }: ConfirmAlertProps) { | ||
| return ( | ||
| <AlertDialog open={open} onOpenChange={onOpenChange}> | ||
| <AlertDialogContent className="z-[60] w-full max-w-[calc(100%-2rem)] sm:max-w-md"> |
There was a problem hiding this comment.
| <AlertDialogContent className="z-[60] w-full max-w-[calc(100%-2rem)] sm:max-w-md"> | |
| <AlertDialogContent className="sm:max-w-md"> |
The AlertDialog portal mounts after the Dialog that means that at equal z-50 it already stacks on top :)
| updatedAt: new Date(), | ||
| }, | ||
| }); | ||
| } catch { |
There was a problem hiding this comment.
| } catch { | |
| } catch { | |
| await admin.auth.admin.deleteUser(userId); |
If the profile insert throws supabase auth user is left behind and the email is now unusable, delete the auth user in the catch
| await db | ||
| .update(profiles) | ||
| .set({ role: role as Role, updatedAt: new Date() }) | ||
| .where(eq(profiles.id, user_id)); |
There was a problem hiding this comment.
| await db | |
| .update(profiles) | |
| .set({ role: role as Role, updatedAt: new Date() }) | |
| .where(eq(profiles.id, user_id)); | |
| try { | |
| await db | |
| .update(profiles) | |
| .set({ role: role as Role, updatedAt: new Date() }) | |
| .where(eq(profiles.id, user_id)); | |
| } catch { | |
| return { | |
| errors: { _form: ["Account was updated but the profile sync failed. Please retry."] }, | |
| }; | |
| } |
feel free to change the error message
| await stripe.subscriptions.create({ | ||
| customer: customerId, | ||
| items: [{ price: priceId }], | ||
| trial_period_days: months * 30, |
There was a problem hiding this comment.
this is incorrect because 30 day months drift from real calendar months, please fix using timestamp:
const trialEnd = new Date();
trialEnd.setMonth(trialEnd.getMonth() + months);
trial_end: Math.floor(trialEnd.getTime() / 1000),Add server actions and Zod schemas for creating and updating users via Supabase Admin API, with profile sync in Drizzle. Add create/edit dialogs and wire the table edit action to EditUserDialog.
Add subscription_months to the create-user flow and grantComplimentarySubscription via STRIPE_PRICE_ID trial. Treat trialing subscriptions as active in the users list and return a generic error when Stripe grant fails after user creation.
Add AlertDialog confirmation before create and save. Remount form state on dialog close so errors and inputs reset. Align action errors with login/discounts patterns and block Stripe-internal messages from the UI. Place Add user above filters with brand styling.
… and harden update error handling
90ad9c6 to
565d963
Compare
Closes #68
Overview
Admins can manage users from
/users: create accounts with email, password, name, and role, optionally grant complimentary subscription months (Stripe trial onSTRIPE_PRICE_ID), and edit existing users’ email and role from the table pencil action.Backend
createUserAdmin/updateUserAdminserver actions with Zod validation andrequireAdmin()createUser/updateUserById) andprofilessync in DrizzlelistUsersWithEmails()joinsauth.usersfor real emails and subscription status for Active/InactivegrantComplimentarySubscription()for optional trial months;syncStripeDatakeepssubscriptionsin syncFrontend
#0040A1alert-dialogcomponent added for confirmationsEnv / setup:
SUPABASE_SECRET_KEY(admin client) andSTRIPE_PRICE_IDmust be set; Stripe product/price must be active for complimentary grants to succeed.Testing
Manually tested the creation and the modification of users.
Screenshots / Screencasts
when you modify the user's email it won't update on the stripe side; I'm not sure that is needed since there's an id tying the two together
Checklist