Skip to content

Sanc 64 update the invitations frontend#47

Merged
Yemyam merged 8 commits intomainfrom
SANC-64-update-the-invitations-frontend
Feb 26, 2026
Merged

Sanc 64 update the invitations frontend#47
Yemyam merged 8 commits intomainfrom
SANC-64-update-the-invitations-frontend

Conversation

@Yemyam
Copy link
Contributor

@Yemyam Yemyam commented Jan 15, 2026

Update invite user and agency modals to match Figma designs

Frontend Changes:

  • Implemented invite modal design from figma
  • Affiliated Agency dropdown now only shows for Agency member role
  • Updated User and Home icons to accept custom stroke colors
  • Added an org role select in the user invite form

NOTE:

  • The agency invite form is currently just hooked up with createOrganization
  • App role (not org role) is not being handled by inviteUser, every user is being added to the db as a driver.

Summary by CodeRabbit

  • New Features

    • Three-part invite flow: select invite type, invite a user, or create an agency.
    • New Agency invite form with name and slug validation.
    • Enhanced User invite form with role selection, conditional organization assignment, and improved validations.
  • UI Improvements

    • Increased modal footer spacing for cleaner layout.
    • Icon components updated to use a consistent, customizable stroke for visual consistency.

@coderabbitai
Copy link

coderabbitai bot commented Jan 15, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a three-part invite flow (type-selection → Invite User modal → Create Agency modal), new InviteAgencyForm component, expanded InviteUserForm with role-based logic and validations, new createAgency and inviteUser mutations wiring success/error handling, modal lifecycle handlers, and adds stroke props to Home/User SVG components. No public API signature changes.

Changes

Cohort / File(s) Summary
Admin Dashboard Invitation Flow
src/app/_components/admincomponents/admin-dashboard.tsx
Replaces single invite modal with a tiered flow: type-selection modal, Invite User modal, and Create Agency modal; adds inviteType state, handleInviteTypeSelect, handleUserConfirm, handleAgencyConfirm, handleCloseModal, and mutations inviteUserMutation and createAgencyMutation; refetches organizations and resets modal state on success.
New Agency Form Component
src/app/_components/admincomponents/invite-agency-form.tsx
Adds InviteAgencyForm component exposing name and slug inputs wired to a Mantine form prop with client-side validation.
User Form Enhancements
src/app/_components/admincomponents/invite-user-form.tsx
Extends InviteUserForm shape to include role; adds role-selection UI, handleRoleChange, conditional organization select (filters system orgs), updated labels/layout, and enhanced validation/behavior for organizationId based on role.
Modal Footer Spacing
src/app/_components/common/modal/modal.tsx
Adjusts default footer spacing from mt="md" to mt="xl".
Icon Stroke Customization
src/assets/icons/home.tsx, src/assets/icons/user.tsx
Adds stroke prop (default #434343) to SVG components and replaces hard-coded stroke values with the prop; simplifies path groups and opacity variants.
Backend Invite Comment
src/server/api/routers/organizations.ts
Small inline comment text change in inviteUser mutation; no functional change or signature modifications.

Sequence Diagram(s)

sequenceDiagram
    actor Admin
    participant Dashboard
    participant Modal
    participant Form
    participant Mutation
    participant API

    Admin->>Dashboard: Click "Invite"
    Dashboard->>Modal: Open type-selection modal
    Admin->>Modal: Choose "User" or "Agency"
    Modal->>Form: Render InviteUserForm or InviteAgencyForm
    Admin->>Form: Fill fields and submit
    Form->>Mutation: Trigger inviteUserMutation / createAgencyMutation
    Mutation->>API: Send HTTP request (inviteUser / createAgency)
    API-->>Mutation: Return success/error
    Mutation->>Dashboard: onSuccess -> refetch orgs, close modal
    Dashboard->>Admin: UI updated (modal closed, org list refreshed)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • burtonjong
  • themaxboucher
  • JustinTan-1
  • wesleylui
  • promatty
  • tanvimahal

Poem

🐰 A tiny hop, a modal bright,

Three-shaped choices in soft moonlight.
Name and slug, an email's cheer,
Invites sent far and near — carrots to steer. 🥕

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Sanc 64 update the invitations frontend' is vague and overly broad, using generic phrasing that doesn't convey the specific changes made. Refine the title to be more descriptive, such as 'Implement three-part invite flow with role selection and agency creation' or 'Redesign user and agency invitation modals per Figma designs'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch SANC-64-update-the-invitations-frontend

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Yemyam Yemyam force-pushed the SANC-64-update-the-invitations-frontend branch from bb0a87e to 80fc82c Compare January 15, 2026 04:41
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@src/app/_components/admincomponents/admin-dashboard.tsx`:
- Line 115: Remove the debug console.log by deleting the line
console.log("submit user", submitData); in the admin-dashboard component (where
submitData is handled) inside admin-dashboard.tsx; if persistent logging is
needed replace it with the app's structured logger call (e.g., using
processLogger or a provided logging utility) or handle the data silently by
returning/setting state, but do not leave console.log in the production
component.
- Line 127: Remove the debug console.log call that prints agencyForm.values in
the submit handler; locate the line console.log("submit agency",
agencyForm.values) inside the admin dashboard submit routine (the agency form
submit function in admin-dashboard.tsx) and delete it (or replace with a proper
structured logger if required by project logging conventions), ensuring no
leftover debug output remains.
- Around line 35-45: The onSuccess handler for createAgencyMutation assumes its
parameter `data` is non-null; add a null-check or non-null assertion before
accessing `data.name` in the onSuccess callback of
api.organization.createOrganization.useMutation (the createAgencyMutation
onSuccess handler) — e.g., return early or guard if `!data`, or assert `data!`
if you’re certain success always provides data, then keep the notify.success,
agencyForm.reset(), setShowInviteModal(false) and organizations.refetch() calls
only when data is present.

In `@src/app/_components/admincomponents/invite-user-form.tsx`:
- Line 100: The fallback is applied after mapping so it never runs; change the
expression to apply the fallback to organizations before mapping, e.g. replace
data={organizations.map(... ) ?? NO_ORGS_DATA} with data={(organizations ??
NO_ORGS_DATA).map(org => ({ value: org.id, label: org.name }))} so the
NO_ORGS_DATA fallback is used when organizations is null/undefined.
🧹 Nitpick comments (4)
src/server/api/routers/organizations.ts (1)

100-106: Consider validating organizationRole conditionally with organizationId.

When organizationId is not provided, organizationRole is still required but ignored (lines 141-149). Consider using Zod's .refine() or discriminated unions to enforce that organizationRole is only required when organizationId is present, or make organizationRole optional as well.

♻️ Suggested schema refinement
     .input(
       z.object({
         name: z.string().min(1),
         email: z.string().email(),
-        organizationRole: z.nativeEnum(OrganizationRole),
+        organizationRole: z.nativeEnum(OrganizationRole).optional(),
         organizationId: z.string().optional(),
-      }),
+      }).refine(
+        (data) => !data.organizationId || data.organizationRole,
+        { message: "organizationRole is required when organizationId is provided", path: ["organizationRole"] }
+      ),
     )

Then update the addMember call:

       if (input.organizationId) {
+        if (!input.organizationRole) {
+          throw new TRPCError({
+            code: "BAD_REQUEST",
+            message: "organizationRole is required when organizationId is provided",
+          });
+        }
         await auth.api.addMember({
src/app/_components/admincomponents/invite-agency-form.tsx (1)

5-12: Consider renaming the interface to avoid shadowing the component name.

Both the interface (InviteAgencyForm) and the component (InviteAgencyForm) share the same name, which can cause confusion and potential import issues. Consider renaming the interface to InviteAgencyFormValues or AgencyFormData.

♻️ Suggested rename
-interface InviteAgencyForm {
+interface InviteAgencyFormValues {
   name: string;
   slug: string;
 }

 interface InviteAgencyFormProps {
-  form: UseFormReturnType<InviteAgencyForm>;
+  form: UseFormReturnType<InviteAgencyFormValues>;
 }
src/app/_components/admincomponents/admin-dashboard.tsx (2)

57-62: Consider importing the shared email regex.

The email regex is already defined in src/types/validation.ts. Importing it would reduce duplication and ensure consistency.

Proposed fix
+import { emailRegex } from "@/types/validation";
...
       email: (value) => {
         if (value.trim().length === 0) return "Email is required";
-        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
         if (!emailRegex.test(value)) return "Invalid email address";
         return null;
       },

131-136: Consider resetting form values on modal close.

Currently only errors are cleared, but form values persist. If a user partially fills the form and closes the modal, stale data will remain when reopening. Consider also calling form.reset() or resetting inviteType to ensure a clean state.

Proposed fix
   const handleCloseModal = () => {
-    userForm.clearErrors();
-    agencyForm.clearErrors();
+    userForm.reset();
+    agencyForm.reset();
     setShowInviteModal(false);
     setInviteType(null);
   };
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 527ad0b and bb0a87e.

📒 Files selected for processing (7)
  • src/app/_components/admincomponents/admin-dashboard.tsx
  • src/app/_components/admincomponents/invite-agency-form.tsx
  • src/app/_components/admincomponents/invite-user-form.tsx
  • src/app/_components/common/modal/modal.tsx
  • src/assets/icons/home.tsx
  • src/assets/icons/user.tsx
  • src/server/api/routers/organizations.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-23T05:44:37.925Z
Learnt from: tanvimahal
Repo: Code-the-Change-YYC/salvationarmy PR: 25
File: src/server/api/routers/organizations.ts:38-38
Timestamp: 2025-12-23T05:44:37.925Z
Learning: In this codebase's TRPC protectedProcedure context, ctx.session has a nested structure containing both a session object and a user object. To access activeOrganizationId, use ctx.session.session.activeOrganizationId (not ctx.session.activeOrganizationId).

Applied to files:

  • src/server/api/routers/organizations.ts
🧬 Code graph analysis (2)
src/app/_components/common/modal/modal.tsx (1)
src/app/_components/common/modal/modaltests.tsx (3)
  • setCustomFooterOpen (153-153)
  • setCustomFooterOpen (158-158)
  • simpleOpen (19-306)
src/app/_components/admincomponents/admin-dashboard.tsx (7)
src/trpc/react.tsx (1)
  • api (25-25)
src/lib/notifications.ts (1)
  • notify (13-58)
src/types/validation.ts (1)
  • emailRegex (10-10)
src/app/_components/common/button/Button.tsx (1)
  • Button (41-80)
src/app/_components/common/modal/modal.tsx (1)
  • Modal (24-70)
src/app/_components/admincomponents/invite-user-form.tsx (1)
  • InviteUserForm (29-108)
src/app/_components/admincomponents/invite-agency-form.tsx (1)
  • InviteAgencyForm (14-38)
🪛 GitHub Actions: CI
src/app/_components/admincomponents/admin-dashboard.tsx

[error] 37-37: yarn tsc --noEmit: TypeScript error TS18047: 'data' is possibly 'null'.

🪛 GitHub Check: ci
src/app/_components/admincomponents/admin-dashboard.tsx

[failure] 37-37:
'data' is possibly 'null'.

🔇 Additional comments (10)
src/app/_components/common/modal/modal.tsx (1)

42-51: LGTM!

The increased margin (mt="xl") provides better visual separation between modal content and footer actions, aligning with the updated Figma designs.

src/assets/icons/user.tsx (1)

4-35: LGTM!

The stroke prop addition enables flexible customization while maintaining backward compatibility with the default #434343 color. The consolidated path structure is cleaner than the previous implementation with duplicate paths.

src/assets/icons/home.tsx (1)

4-35: LGTM!

Consistent implementation with the User icon. The stroke prop customization pattern across icons enables unified styling in the modal invitation flow.

src/server/api/routers/organizations.ts (2)

125-131: LGTM on the name field integration.

The name field is properly validated at input and correctly passed to signUpEmail. This ensures invited users have a name set from the start.


141-149: Behavior is intentional: non-MEMBER roles are created without organization association.

The code correctly implements the design where only users with the MEMBER role are associated with an organization. Frontend validation enforces this by requiring organizationId only for MEMBER role and clearing it for non-member roles (ADMIN, DRIVER). When organizationId is omitted or not provided for other roles, the user is created successfully without organization membership, which is the expected behavior.

src/app/_components/admincomponents/invite-agency-form.tsx (2)

14-37: LGTM on component structure.

The form component follows Mantine's recommended patterns with form.key() for React keys and getInputProps() for field binding. The layout is clean and accessible with proper labels.


28-34: Slug validation and format guidance already exist in the parent form.

The form validation is implemented in admin-dashboard.tsx (lines 81-84) with a regex pattern /^[a-z0-9-]+$/ that enforces lowercase letters, numbers, and hyphens. The validation error message clearly communicates the required format: "Slug must only contain lowercase letters, numbers, and hyphens". Additionally, the field label already provides a format example ("Agency Slug (ex: my-org)"). No changes are needed.

Likely an incorrect or invalid review comment.

src/app/_components/admincomponents/invite-user-form.tsx (1)

23-27: Verify: OWNER role labeled as "Driver" is intentional.

The mapping of OrganizationRole.OWNER to the label "Driver" appears semantically unusual. Please confirm this aligns with the business logic and Figma designs. If "Driver" is indeed a distinct role concept, consider renaming the enum value for clarity.

src/app/_components/admincomponents/admin-dashboard.tsx (2)

147-186: Approve: Two-step invitation flow is well-structured.

The type selection modal cleanly separates concerns with clear UX. The showDefaultFooter={false} correctly hides the confirm/cancel buttons since selection is done via the type buttons. The onConfirm={() => {}} no-op is acceptable here since confirmation isn't needed for this step.


188-228: LGTM: Form modals properly integrated.

The conditional rendering of user/agency modals based on inviteType, with appropriate loading states and form passing, is correctly implemented.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/server/api/routers/organizations.ts`:
- Around line 99-106: The inviteUser adminProcedure input schema currently
allows any organizationRole with an optional organizationId; update the Zod
schema for inviteUser to enforce the frontend contract by adding a .refine() on
the object: require organizationId when organizationRole ===
OrganizationRole.MEMBER (and optionally reject organizationId for non-MEMBER
roles if you prefer strictness). Reference the inviteUser input object and
OrganizationRole enum when adding the refine so validation fails if MEMBER is
missing organizationId (or if non-MEMBER includes one, depending on chosen
strictness).
♻️ Duplicate comments (2)
src/app/_components/admincomponents/admin-dashboard.tsx (2)

115-116: Remove debug console.log statement.

This debug statement should be removed before merging.


127-128: Remove debug console.log statement.

This debug statement should be removed before merging.

🧹 Nitpick comments (4)
src/server/api/routers/organizations.ts (1)

110-119: Consider removing redundant authorization check.

This manual role check duplicates the protection already provided by adminProcedure. While defense-in-depth is valid, consider whether this adds value or just maintenance overhead.

src/app/_components/admincomponents/admin-dashboard.tsx (3)

57-62: Import shared email regex from validation module.

The email regex is duplicated here. Consider importing from src/types/validation.ts which already exports emailRegex to maintain a single source of truth.

♻️ Proposed fix
+import { emailRegex } from "@/types/validation";

Then in the validation:

       email: (value) => {
         if (value.trim().length === 0) return "Email is required";
-        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
         if (!emailRegex.test(value)) return "Invalid email address";
         return null;
       },

147-159: Remove unnecessary onConfirm prop.

Since showDefaultFooter={false}, the onConfirm={() => {}} is never invoked. Consider removing it for clarity, unless the Modal component requires it.

♻️ Proposed fix
         <Modal
           opened={showInviteModal}
           onClose={handleCloseModal}
-          onConfirm={() => {}}
           title={

209-228: Clarify modal title: "Create" vs "Invite" an Agency.

The modal title says "Invite an Agency" but the action creates an organization via createOrganization. Consider changing the title to "Create an Agency" or "Add an Agency" to more accurately reflect the action.

♻️ Proposed fix
           title={
             <Box fw={600} fz="xl">
-              Invite an Agency
+              Create an Agency
             </Box>
           }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bb0a87e and 80fc82c.

📒 Files selected for processing (5)
  • src/app/_components/admincomponents/admin-dashboard.tsx
  • src/app/_components/admincomponents/invite-agency-form.tsx
  • src/assets/icons/home.tsx
  • src/assets/icons/user.tsx
  • src/server/api/routers/organizations.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/assets/icons/home.tsx
  • src/app/_components/admincomponents/invite-agency-form.tsx
  • src/assets/icons/user.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-23T05:44:37.925Z
Learnt from: tanvimahal
Repo: Code-the-Change-YYC/salvationarmy PR: 25
File: src/server/api/routers/organizations.ts:38-38
Timestamp: 2025-12-23T05:44:37.925Z
Learning: In this codebase's TRPC protectedProcedure context, ctx.session has a nested structure containing both a session object and a user object. To access activeOrganizationId, use ctx.session.session.activeOrganizationId (not ctx.session.activeOrganizationId).

Applied to files:

  • src/server/api/routers/organizations.ts
🧬 Code graph analysis (2)
src/server/api/routers/organizations.ts (1)
src/lib/auth.ts (1)
  • auth (10-116)
src/app/_components/admincomponents/admin-dashboard.tsx (7)
src/trpc/react.tsx (1)
  • api (25-25)
src/lib/notifications.ts (1)
  • notify (13-58)
src/types/validation.ts (1)
  • emailRegex (10-10)
src/app/_components/common/button/Button.tsx (1)
  • Button (41-80)
src/app/_components/common/modal/modal.tsx (1)
  • Modal (24-70)
src/app/_components/admincomponents/invite-user-form.tsx (1)
  • InviteUserForm (29-108)
src/app/_components/admincomponents/invite-agency-form.tsx (1)
  • InviteAgencyForm (14-38)
🔇 Additional comments (4)
src/server/api/routers/organizations.ts (1)

141-149: LGTM! Conditional organization membership is correctly implemented.

The logic correctly gates addMember on the presence of organizationId, allowing user creation without immediate organization membership when appropriate.

src/app/_components/admincomponents/admin-dashboard.tsx (3)

35-45: LGTM! Null-safety addressed.

The optional chaining data?.name ?? "Agency" correctly handles the potentially null data response.


73-88: LGTM! Agency form validation is well-implemented.

The slug validation correctly enforces URL-safe format with lowercase letters, numbers, and hyphens.


90-117: LGTM! Submission data correctly prepared.

The conditional inclusion of organizationId only for MEMBER role aligns well with the backend changes that gate addMember on the presence of organizationId.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines 99 to 106
inviteUser: adminProcedure
.input(
z.object({
name: z.string().min(1),
email: z.string().email(),
organizationRole: z.nativeEnum(OrganizationRole),
organizationId: z.string(),
organizationId: z.string().optional(),
}),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cd src/server/api/routers && wc -l organizations.ts

Repository: Code-the-Change-YYC/salvationarmy

Length of output: 97


🏁 Script executed:

cd src/server/api/routers && sed -n '90,160p' organizations.ts

Repository: Code-the-Change-YYC/salvationarmy

Length of output: 2216


🏁 Script executed:

rg "organizationRole" --type=ts -B 3 -A 3 | head -100

Repository: Code-the-Change-YYC/salvationarmy

Length of output: 7374


Enforce the relationship between organizationRole and organizationId in the schema.

The frontend shows that organizationId is conditionally required only when organizationRole is MEMBER. However, the schema accepts any combination, allowing organizationRole (required) without organizationId when the role is non-MEMBER, even though the API won't use it. Use Zod's .refine() to enforce that organizationId is required when organizationRole is MEMBER, or adjust the schema to reflect that non-MEMBER roles should not include organizationId at all.

🤖 Prompt for AI Agents
In `@src/server/api/routers/organizations.ts` around lines 99 - 106, The
inviteUser adminProcedure input schema currently allows any organizationRole
with an optional organizationId; update the Zod schema for inviteUser to enforce
the frontend contract by adding a .refine() on the object: require
organizationId when organizationRole === OrganizationRole.MEMBER (and optionally
reject organizationId for non-MEMBER roles if you prefer strictness). Reference
the inviteUser input object and OrganizationRole enum when adding the refine so
validation fails if MEMBER is missing organizationId (or if non-MEMBER includes
one, depending on chosen strictness).

@Yemyam Yemyam force-pushed the SANC-64-update-the-invitations-frontend branch from 80fc82c to 4329fdd Compare January 15, 2026 04:54
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this file, what is the distinction between the invite types of "user", "agency" and null?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm using invite type to determine which modal to display. "user" for the user modal, "agency" for the agency modal, and null for the selection modal to select between inviting a user and agency.

Copy link
Contributor

@burtonjong burtonjong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's some confusion about these organization roles

Maybe we can hop on a call and i can explain it better if you still don't understand from my comments.

We would use these organizations in the future for these example cases:

not everyone in the salvation army navigation center should be able to invite new agencies. This could be done by appointing ADMINs (org role) and MEMBERs (org role) that have different permissions. Admins will be able to send invitations, and members wont be able to

Or even for drivers, ADMIN drivers will be able to see ALL bookings whereas MEMBER drivers won't, and only bookings assigned to them, etc.

@vercel
Copy link

vercel bot commented Feb 4, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
salvationarmy Ready Ready Preview, Comment Feb 26, 2026 5:11am

<Stack gap="md">
<Box fw={500} fz="lg">
User Information
<Box fw={600} fz="lg" c="#8B2635">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this color is hardcoded here a few times. can we import from the global scss file instead

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we use colors from globals?

@burtonjong
Copy link
Contributor

@Yemyam I think once you address matthews comments and then change the "invite agency" to "create agency" this looks good

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
src/app/_components/admincomponents/invite-user-form.tsx (2)

69-89: Redundant inline backgroundColor on filled-variant buttons.

When variant="filled" is active, Mantine's color prop already controls the background. The inline style={{ backgroundColor: ... }} duplicates this. It's harmless but unnecessary.

Proposed cleanup
                 onClick={() => handleRoleChange(role)}
                 style={{
                   flex: 1,
-                  backgroundColor: selectedRole === role ? "#8B2635" : undefined,
                 }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/_components/admincomponents/invite-user-form.tsx` around lines 69 -
89, Remove the redundant inline backgroundColor in the Button style: inside the
ALL_ROLES.map render where Button components are created (using props variant,
color, leftSection, onClick calling handleRoleChange, and children
ROLE_LABELS[role]), delete the backgroundColor entry from the style object but
keep other style properties like flex: 1; rely on Mantine's variant="filled" and
the color prop (which already sets the filled background when selectedRole ===
role) to control the button background.

8-8: Consider extracting SYSTEM_ORG_SLUGS to a shared constant.

This same list ("admins", "drivers") is defined in invite-user-form.tsx (line 8) but the same slugs are hard-coded as string literals in admin-dashboard.tsx (lines 105 and 112). Creating a shared constant would prevent divergence and ensure these slugs stay in sync if they ever need to change.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/_components/admincomponents/invite-user-form.tsx` at line 8, Extract
the array constant SYSTEM_ORG_SLUGS (["admins","drivers"]) into a shared module
(e.g., a new export from a central constants file) and replace the duplicate
string literals in admin-dashboard.tsx with an imported reference to that shared
SYSTEM_ORG_SLUGS; update invite-user-form.tsx to import the shared
SYSTEM_ORG_SLUGS instead of defining it locally and ensure both files import the
same symbol so the slugs remain in sync.
src/app/_components/admincomponents/admin-dashboard.tsx (1)

158-199: No way to navigate back from a form modal to the type-selection modal.

Once a user selects "Invite User" or "Create Agency," the only escape is closing the modal entirely. A "Back" button or breadcrumb to return to type selection would improve UX. This could be deferred if not in the current Figma spec.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/_components/admincomponents/admin-dashboard.tsx` around lines 158 -
199, The modal flow lacks a way to return from the form view to the initial
type-selection view; add a "Back" control in the form modal (the Modal rendered
when inviteType !== null) that calls a handler (e.g., handleBackToTypeSelection)
to set inviteType back to null while keeping showInviteModal true, so the Modal
re-renders the type-selection UI; update the Modal footer or header in the
branch that uses inviteType (and functions like handleInviteTypeSelect,
handleCloseModal) to include this Back action and ensure any form state is reset
when toggling back.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/_components/admincomponents/admin-dashboard.tsx`:
- Around line 144-149: handleCloseModal currently only clears validation
messages (userForm.clearErrors(), agencyForm.clearErrors()) so previous input
remains when reopening; update handleCloseModal to also reset form values by
calling userForm.reset() and agencyForm.reset() (or userForm.reset(defaults) /
agencyForm.reset(defaults) if you rely on initial values), then continue to
setShowInviteModal(false) and setInviteType(null) so both cancel/close and
onSuccess paths produce the same cleared state.
- Around line 36-43: In createAgencyMutation's onSuccess handler, don't silently
return when data is falsy; instead notify the user (e.g., call notify.warn or
notify.error with a message like "No agency data returned from server") and
optionally log the unexpected response, and only perform agencyForm.reset(),
setShowInviteModal(false), and organizations.refetch() when data is present;
update the guard in onSuccess to show the warning and exit without closing the
modal or resetting the form so the user receives feedback.

In `@src/app/_components/admincomponents/invite-user-form.tsx`:
- Around line 92-102: The Organization Permission Level Select (symbols: Select,
OrganizationRole, form.getInputProps("organizationRole")) is always shown but
should be hidden when the chosen invitee role maps to the system org (as handled
in admin-dashboard.tsx); update the component to conditionally render the Select
only when the selected role is organization-scoped (e.g., check
form.getInputProps("role") or form.values.role and render the organizationRole
Select only if role !== OrganizationRole.ADMIN && role !== "Driver" (or whatever
admin-dashboard.tsx maps to system org)). Ensure you reference the same form
field name used for the invitee role and keep the existing key and input props
for organizationRole when rendering.

---

Nitpick comments:
In `@src/app/_components/admincomponents/admin-dashboard.tsx`:
- Around line 158-199: The modal flow lacks a way to return from the form view
to the initial type-selection view; add a "Back" control in the form modal (the
Modal rendered when inviteType !== null) that calls a handler (e.g.,
handleBackToTypeSelection) to set inviteType back to null while keeping
showInviteModal true, so the Modal re-renders the type-selection UI; update the
Modal footer or header in the branch that uses inviteType (and functions like
handleInviteTypeSelect, handleCloseModal) to include this Back action and ensure
any form state is reset when toggling back.

In `@src/app/_components/admincomponents/invite-user-form.tsx`:
- Around line 69-89: Remove the redundant inline backgroundColor in the Button
style: inside the ALL_ROLES.map render where Button components are created
(using props variant, color, leftSection, onClick calling handleRoleChange, and
children ROLE_LABELS[role]), delete the backgroundColor entry from the style
object but keep other style properties like flex: 1; rely on Mantine's
variant="filled" and the color prop (which already sets the filled background
when selectedRole === role) to control the button background.
- Line 8: Extract the array constant SYSTEM_ORG_SLUGS (["admins","drivers"])
into a shared module (e.g., a new export from a central constants file) and
replace the duplicate string literals in admin-dashboard.tsx with an imported
reference to that shared SYSTEM_ORG_SLUGS; update invite-user-form.tsx to import
the shared SYSTEM_ORG_SLUGS instead of defining it locally and ensure both files
import the same symbol so the slugs remain in sync.

Comment on lines 36 to 43
const createAgencyMutation = api.organization.createOrganization.useMutation({
onSuccess: (data) => {
if (!data) return;
notify.success(`Agency "${data.name}" created successfully`);
agencyForm.reset();
setShowInviteModal(false);
void organizations.refetch();
},
Copy link

@coderabbitai coderabbitai bot Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Silent early return on null data — user gets no feedback.

The if (!data) return; guard silently swallows the case. The mutation's onSuccess fired, so the UI won't show a loading state, but the modal stays open with no notification. Consider at least a warning.

Proposed fix
    onSuccess: (data) => {
-     if (!data) return;
+     if (!data) {
+       notify.error("Unexpected error: no data returned");
+       return;
+     }
      notify.success(`Agency "${data.name}" created successfully`);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const createAgencyMutation = api.organization.createOrganization.useMutation({
onSuccess: (data) => {
if (!data) return;
notify.success(`Agency "${data.name}" created successfully`);
agencyForm.reset();
setShowInviteModal(false);
void organizations.refetch();
},
const createAgencyMutation = api.organization.createOrganization.useMutation({
onSuccess: (data) => {
if (!data) {
notify.error("Unexpected error: no data returned");
return;
}
notify.success(`Agency "${data.name}" created successfully`);
agencyForm.reset();
setShowInviteModal(false);
void organizations.refetch();
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/_components/admincomponents/admin-dashboard.tsx` around lines 36 -
43, In createAgencyMutation's onSuccess handler, don't silently return when data
is falsy; instead notify the user (e.g., call notify.warn or notify.error with a
message like "No agency data returned from server") and optionally log the
unexpected response, and only perform agencyForm.reset(),
setShowInviteModal(false), and organizations.refetch() when data is present;
update the guard in onSuccess to show the warning and exit without closing the
modal or resetting the form so the user receives feedback.

✅ Confirmed as addressed by @promatty

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i mean i dont see this happening since we return the org object, but to be safe lets add error handling

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@promatty, that makes sense! Even though the API should always return the organization object, adding the error handling is good defensive programming. It ensures users get proper feedback in the unlikely edge case where something unexpected happens.

Thanks for addressing this! 🎉


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

Copy link
Contributor

@promatty promatty left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

small changes, approving now so you can merge after fixing them

<Stack gap="md">
<Box fw={500} fz="lg">
User Information
<Box fw={600} fz="lg" c="#8B2635">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we use colors from globals?

Comment on lines 36 to 43
const createAgencyMutation = api.organization.createOrganization.useMutation({
onSuccess: (data) => {
if (!data) return;
notify.success(`Agency "${data.name}" created successfully`);
agencyForm.reset();
setShowInviteModal(false);
void organizations.refetch();
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i mean i dont see this happening since we return the org object, but to be safe lets add error handling

@Yemyam Yemyam merged commit b749565 into main Feb 26, 2026
4 checks passed
@Yemyam Yemyam deleted the SANC-64-update-the-invitations-frontend branch February 26, 2026 05:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants