Skip to content

Comments

Prospective sponsors#3974

Open
walker-sean wants to merge 9 commits intodevelopfrom
prospective-sponsors
Open

Prospective sponsors#3974
walker-sean wants to merge 9 commits intodevelopfrom
prospective-sponsors

Conversation

@walker-sean
Copy link
Member

@walker-sean walker-sean commented Feb 15, 2026

Changes

  • Prospective Sponsors: New table and feature for tracking companies that haven't committed to sponsoring yet, with a full lifecycle from initial contact to acceptance as a full sponsor.
  • Sponsor Value Types: Sponsors can now be tagged as MONETARY, STOCK, DISCOUNT, or any combination. sponsorValue is now optional (only required for monetary sponsors), and new stockDescription and discountDescription fields capture non-monetary terms.
  • Sponsor Tier Optional: Sponsor tier is no longer required when creating or editing a sponsor.
  • NOT_IN_CONTACT default: When creating a prospective sponsor, the default status is now NOT_IN_CONTACT. Prospective sponsors in this status do not require a contact, contactor, first contact method, or last contact date — those fields are hidden in the form and only become required when the status is changed to anything else.
  • Sponsor Tasks: Tasks can be attached to both full sponsors and prospective sponsors. Tasks support due dates, notify dates, assignees, notes, and a done checkbox. Slack notifications are sent to assignees on task creation for both sponsor types.
  • Accept Prospective Sponsor: Heads can accept a prospective sponsor directly from the table, which creates a full sponsor record and soft-deletes the prospective sponsor.
  • There is a time threshold beyond which the row will be highlighted on the table

Notes

  • The Prospective_Sponsor table stores contactorUserId, contactId, and firstContactMethod as nullable columns, allowing NOT_IN_CONTACT entries to have no contact info. Transitioning to a non-NOT_IN_CONTACT status creates a contact record if one doesn't exist yet; transitioning back to NOT_IN_CONTACT clears it.
  • Sponsor_Task is shared between full sponsors (sponsorId) and prospective sponsors (prospectiveSponsorId) — both are nullable FKs on the same table.
  • At least one of contact email or phone is required when a prospective sponsor is not in NOT_IN_CONTACT status (validated on both frontend and backend).

Screenshots

image image image image image image

Checklist

It can be helpful to check the Checks and Files changed tabs.
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.

  • All commits are tagged with the ticket number
  • No linting errors / newline at end of file warnings
  • All code follows repository-configured prettier formatting
  • No merge conflicts
  • All checks passing
  • Screenshots of UI changes (see Screenshots section)
  • Remove any non-applicable sections of this template
  • Assign the PR to yourself
  • No yarn.lock changes (unless dependencies have changed)
  • Request reviewers & ping on Slack
  • PR is linked to the ticket (fill in the closes line below)

Closes # (issue #)

@walker-sean walker-sean marked this pull request as ready for review February 23, 2026 23:04
Copilot AI review requested due to automatic review settings February 23, 2026 23:04
@walker-sean walker-sean marked this pull request as draft February 23, 2026 23:05
@walker-sean walker-sean marked this pull request as draft February 23, 2026 23:05
@walker-sean walker-sean review requested due to automatic review settings February 23, 2026 23:06
@walker-sean walker-sean marked this pull request as ready for review February 24, 2026 00:08
Copilot AI review requested due to automatic review settings February 24, 2026 00:08
@walker-sean walker-sean requested review from Copilot and removed request for Copilot February 24, 2026 00:16
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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.

Comment on lines +148 to +163
<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'} />}
/>
)}
/>
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines 71 to 106
@@ -91,6 +101,7 @@ const SponsorTasksModal: React.FC<SponsorTasksModalProps> = ({ onClose, sponsor
}
}
});
toast.success('Tasks saved successfully!');
onClose();
});
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +785 to +787
return axios.post<SponsorTask>(apiUrls.toggleSponsorTaskDone(sponsorTaskId), {
transformResponse: (data: string) => JSON.parse(data)
});
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

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).

Suggested change
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)
}
);

Copilot uses AI. Check for mistakes.
Comment on lines +866 to +868
return axios.post<ProspectiveSponsor>(apiUrls.deleteProspectiveSponsor(prospectiveSponsorId), {
transformResponse: (data: string) => prospectiveSponsorTransformer(JSON.parse(data))
});
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
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))
}
);

Copilot uses AI. Check for mistakes.
Comment on lines +112 to +123
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);
}
});
}
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

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).

Copilot uses AI. Check for mistakes.
Comment on lines +453 to +455
};

export default prospectiveSponsorSchema;
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines 21 to 63
@@ -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
}));
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
@walker-sean walker-sean requested a review from chpy04 February 24, 2026 00:25
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.

1 participant