Conversation
There was a problem hiding this comment.
Pull request overview
Adds a “Prospective Sponsors” workflow to the finance area, expanding sponsor modeling to support non-monetary sponsorship types and shared task tracking across both sponsors and prospective sponsors.
Changes:
- Introduces prospective sponsor data model + CRUD/API + UI table, including acceptance flow to convert into a full sponsor.
- Updates sponsor model to support multiple value types (MONETARY/STOCK/DISCOUNT), optional tier/value, and structured contact info.
- Adds shared sponsor/prospective-sponsor task support with “done” state and Slack notifications.
Reviewed changes
Copilot reviewed 48 out of 48 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/shared/src/types/finance-types.ts | Adds shared types/enums for prospective sponsors, contact info, value types, and task “done”. |
| src/shared/index.ts | Adjusts shared exports list (finance types exposure). |
| src/frontend/src/utils/urls.ts | Adds endpoints for prospective sponsors and toggling task done. |
| src/frontend/src/utils/teams.utils.ts | Extends submit button text union to include “Accept”. |
| src/frontend/src/pages/FinancePage/VendorsAndSponsorsPage.tsx | Adds a new “Prospective Sponsors” tab and rendering logic. |
| src/frontend/src/pages/FinancePage/SponsorsTable.tsx | Updates sponsors table columns for new contact/value types model and task modal wrapper. |
| src/frontend/src/pages/FinancePage/ProspectiveSponsorsTable.tsx | New table UI for prospective sponsors (highlighting, accept/delete/tasks). |
| src/frontend/src/pages/FinancePage/FinanceComponents/SponsorTasksModalWrapper.tsx | Wrapper to fetch sponsor tasks and pass create hook into modal. |
| src/frontend/src/pages/FinancePage/FinanceComponents/SponsorTasksModal.tsx | Refactors task UI into card-based layout, adds “done”, delete-on-save behavior. |
| src/frontend/src/pages/FinancePage/FinanceComponents/SponsorTaskCard.tsx | New reusable task editor card (dates, assignee, notes, done checkbox). |
| src/frontend/src/pages/FinancePage/FinanceComponents/SponsorForm.tsx | Updates sponsor form for value types, optional tier, structured contact fields, task cards. |
| src/frontend/src/pages/FinancePage/FinanceComponents/SidePagePopup.tsx | Tweaks side panel layout spacing/scrolling. |
| src/frontend/src/pages/FinancePage/FinanceComponents/ProspectiveSponsorTasksModal.tsx | Wrapper for prospective sponsor tasks using shared task modal. |
| src/frontend/src/pages/FinancePage/FinanceComponents/ProspectiveSponsorForm.tsx | New prospective sponsor create/edit form with NOT_IN_CONTACT conditional requirements. |
| src/frontend/src/pages/FinancePage/FinanceComponents/EditSponsorPage.tsx | Updates edit sponsor defaults and payload mapping for new fields; adds toasts. |
| src/frontend/src/pages/FinancePage/FinanceComponents/EditProspectiveSponsorPage.tsx | New prospective sponsor edit side page with toasts + task defaults. |
| src/frontend/src/pages/FinancePage/FinanceComponents/DeleteSponsorTaskModal.tsx | Adds toast + awaits deletion mutation. |
| src/frontend/src/pages/FinancePage/FinanceComponents/DeleteSponsor.tsx | Adds toast + awaits deletion mutation. |
| src/frontend/src/pages/FinancePage/FinanceComponents/DeleteProspectiveSponsorModal.tsx | New delete modal for prospective sponsors (toast + mutation). |
| src/frontend/src/pages/FinancePage/FinanceComponents/CreateSponsorPage.tsx | Updates sponsor creation defaults for new fields; adds toasts. |
| src/frontend/src/pages/FinancePage/FinanceComponents/CreateProspectiveSponsorPage.tsx | New create flow for prospective sponsors with NOT_IN_CONTACT default. |
| src/frontend/src/pages/FinancePage/FinanceComponents/AcceptProspectiveSponsorModal.tsx | New accept/convert flow UI to create a sponsor from a prospective sponsor. |
| src/frontend/src/hooks/finance.hooks.ts | Adds hooks for prospective sponsors + toggle task done; updates sponsor payload types. |
| src/frontend/src/components/NERDataGrid.tsx | Adds row class callback + custom DataGrid sx passthrough for highlighting. |
| src/frontend/src/apis/transformers/prospective-sponsor.transformer.ts | Adds frontend transformer to normalize dates for prospective sponsors + nested tasks. |
| src/frontend/src/apis/finance.api.ts | Adds prospective sponsor APIs + toggle done API integration. |
| src/backend/tests/unit/prospective-sponsor.test.ts | New unit tests covering prospective sponsor lifecycle + validation. |
| src/backend/tests/unit/finance.test.ts | Updates tests for sponsor model changes + adds toggle-done tests. |
| src/backend/tests/test-utils.ts | Ensures prospective sponsors are cleared between tests. |
| src/backend/src/utils/reimbursement-requests.utils.ts | Makes finance/head auth checks resilient if finance team doesn’t exist; adds helper. |
| src/backend/src/utils/errors.utils.ts | Extends exception object names to include ProspectiveSponsor/SponsorTier. |
| src/backend/src/transformers/sponsor-task.transformer.ts | Adds done field to sponsor task transformer. |
| src/backend/src/transformers/prospective-sponsor.transformer.ts | New transformer for prospective sponsors (contact, contactor, tasks). |
| src/backend/src/transformers/finance.transformer.ts | Updates sponsor transformer for new contact/valueTypes/tier optionality. |
| src/backend/src/services/reimbursement-requests.services.ts | Uses new isUserFinanceTeamOrHead helper for consistent auth checks. |
| src/backend/src/services/prospective-sponsor.services.ts | New service implementing prospective sponsor lifecycle, tasks, and accept flow. |
| src/backend/src/services/notifications.services.ts | Extends task reminder notifications to prospective sponsor tasks. |
| src/backend/src/services/finance.services.ts | Updates sponsor CRUD + task CRUD for new fields, shared tasks, and toggle-done. |
| src/backend/src/routes/prospective-sponsor.routes.ts | New API routes for prospective sponsors and their tasks/accept. |
| src/backend/src/routes/finance.routes.ts | Updates sponsor create/edit validations and adds toggle-done endpoint. |
| src/backend/src/prisma/seed.ts | Seeds new sponsor fields and adds sample prospective sponsors + tasks. |
| src/backend/src/prisma/schema.prisma | Adds prospective sponsor/contact/value-types schema + shared task FKs and done flag. |
| src/backend/src/prisma/migrations/20260205202908_prospective_sponsors/migration.sql | Migration for new tables/enums and sponsor/contact refactor. |
| src/backend/src/prisma-query-args/sponsor.query.args.ts | Ensures sponsor queries include contact. |
| src/backend/src/prisma-query-args/prospective-sponsor.query-args.ts | New query args include contactor/tasks/contact for prospective sponsors. |
| src/backend/src/controllers/prospective-sponsor.controllers.ts | New controller wiring req body/params into service calls. |
| src/backend/src/controllers/finance.controllers.ts | Updates sponsor create/edit mapping + supports editTask done + toggle-done handler. |
| src/backend/index.ts | Registers the new prospective sponsors router. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <Controller | ||
| control={control} | ||
| name={`${fieldPrefix}.assigneeUserId`} | ||
| render={({ field }) => ( | ||
| <Autocomplete | ||
| options={members} | ||
| getOptionLabel={(option) => `${option.firstName} ${option.lastName}`} | ||
| isOptionEqualToValue={(option, value) => option.userId === value.userId} | ||
| value={members.find((u) => u.userId === field.value) || null} | ||
| onChange={(_, newValue) => field.onChange(newValue?.userId || undefined)} | ||
| size="small" | ||
| fullWidth | ||
| renderInput={(params) => <TextField {...params} placeholder={defaultAssigneeName || 'Select Member'} />} | ||
| /> | ||
| )} | ||
| /> |
There was a problem hiding this comment.
SponsorTaskCard hard-codes the assignee field to ${fieldPrefix}.assigneeUserId, but SponsorTasksModal currently models the field as assignee. This makes the shared card incompatible with that form. Consider either standardizing task forms on assigneeUserId or adding a prop (e.g., assigneeFieldName) so the card can be reused safely.
| @@ -91,6 +101,7 @@ const SponsorTasksModal: React.FC<SponsorTasksModalProps> = ({ onClose, sponsor | |||
| } | |||
| } | |||
| }); | |||
| toast.success('Tasks saved successfully!'); | |||
| onClose(); | |||
| }); | |||
There was a problem hiding this comment.
try/catch around editTask(...)/createTask(...) won’t catch request failures because these are React Query mutate functions (async errors surface via onError, not thrown synchronously). The UI also shows a success toast and closes immediately even if mutations fail. Use mutateAsync and await (e.g., Promise.all) and only toast/close after successful completion; handle errors via caught exceptions or onError.
| return axios.post<SponsorTask>(apiUrls.toggleSponsorTaskDone(sponsorTaskId), { | ||
| transformResponse: (data: string) => JSON.parse(data) | ||
| }); |
There was a problem hiding this comment.
toggleSponsorTaskDone passes transformResponse as the POST body (2nd argument). In Axios, transformResponse belongs in the request config (3rd argument), otherwise you’ll send an unexpected request payload and the response won’t be transformed. Move transformResponse into the config param (and pass an empty body if needed).
| return axios.post<SponsorTask>(apiUrls.toggleSponsorTaskDone(sponsorTaskId), { | |
| transformResponse: (data: string) => JSON.parse(data) | |
| }); | |
| return axios.post<SponsorTask>( | |
| apiUrls.toggleSponsorTaskDone(sponsorTaskId), | |
| null, | |
| { | |
| transformResponse: (data: string) => JSON.parse(data) | |
| } | |
| ); |
| return axios.post<ProspectiveSponsor>(apiUrls.deleteProspectiveSponsor(prospectiveSponsorId), { | ||
| transformResponse: (data: string) => prospectiveSponsorTransformer(JSON.parse(data)) | ||
| }); |
There was a problem hiding this comment.
deleteProspectiveSponsor has the same Axios call shape issue as toggleSponsorTaskDone: transformResponse is being sent as the request body instead of being provided as the request config. This likely breaks date transformations and may send an unintended payload to the backend.
| return axios.post<ProspectiveSponsor>(apiUrls.deleteProspectiveSponsor(prospectiveSponsorId), { | |
| transformResponse: (data: string) => prospectiveSponsorTransformer(JSON.parse(data)) | |
| }); | |
| return axios.post<ProspectiveSponsor>( | |
| apiUrls.deleteProspectiveSponsor(prospectiveSponsorId), | |
| null, | |
| { | |
| transformResponse: (data: string) => prospectiveSponsorTransformer(JSON.parse(data)) | |
| } | |
| ); |
| if (tasks?.length) { | ||
| tasks.forEach(async (task) => { | ||
| if (!task.assigneeUserId) return; | ||
| const assignee = await prisma.user.findUnique({ | ||
| where: { userId: task.assigneeUserId }, | ||
| include: { userSettings: true } | ||
| }); | ||
| if (assignee) { | ||
| await notifySponsorTaskAssignee(assignee, task, organizationName); | ||
| } | ||
| }); | ||
| } |
There was a problem hiding this comment.
This uses tasks.forEach(async ...) without awaiting the resulting promises. That can lead to unhandled rejections and notifications being dropped if the request finishes after the service returns (or the process errors). Prefer building an array of promises and await Promise.all(...) (or explicitly void the promises with proper error handling/logging).
| }; | ||
|
|
||
| export default prospectiveSponsorSchema; |
There was a problem hiding this comment.
This file’s default export is prospectiveSponsorSchema, but the filename and primary component export suggest the default should be the form component (or there should be no default export). Keeping a schema as the default export is confusing and easy to import incorrectly; consider removing the default export or default-exporting ProspectiveSponsorForm instead.
| @@ -49,29 +49,39 @@ const SponsorTasksModal: React.FC<SponsorTasksModalProps> = ({ onClose, sponsor | |||
| defaultValues: { tasks: [] } | |||
| }); | |||
|
|
|||
| const { fields, append } = useFieldArray({ control, name: 'tasks' }); | |||
| const { fields, append, remove } = useFieldArray({ control, name: 'tasks' }); | |||
|
|
|||
| useEffect(() => { | |||
| if (sponsorTasks) { | |||
| reset({ | |||
| tasks: sponsorTasks.map((task) => ({ | |||
| sponsorTaskId: task.sponsorTaskId, | |||
| dueDate: task.dueDate ? new Date(task.dueDate) : new Date(), | |||
| notifyDate: task.notifyDate ? new Date(task.notifyDate) : undefined, | |||
| assignee: task.assignee?.userId || '', | |||
| notes: task.notes || '' | |||
| })) | |||
| }); | |||
| const mapped = sponsorTasks.map((task) => ({ | |||
| sponsorTaskId: task.sponsorTaskId, | |||
| dueDate: task.dueDate ? new Date(task.dueDate) : new Date(), | |||
| notifyDate: task.notifyDate ? new Date(task.notifyDate) : undefined, | |||
| assignee: task.assignee?.userId || '', | |||
| notes: task.notes || '', | |||
| done: task.done || false | |||
| })); | |||
There was a problem hiding this comment.
In this modal the form model uses assignee (see schema + mapping + save payload), but SponsorTaskCard binds the assignee picker to ${fieldPrefix}.assigneeUserId. As a result, selecting an assignee in the UI won’t update the field that handleSave reads, so assignee changes can be lost. Align the form field name with the card (e.g., use assigneeUserId throughout the modal schema/mapping/save) or make SponsorTaskCard accept the assignee field name as a prop.
Changes
Notes
Screenshots
Checklist
It can be helpful to check the
ChecksandFiles changedtabs.Please review the contributor guide and reach out to your Tech Lead if anything is unclear.
Please request reviewers and ping on slack only after you've gone through this whole checklist.
yarn.lockchanges (unless dependencies have changed)Closes # (issue #)