Petition Management
5.1 getPetitionContextData
Location: src/lib/getPetitionContextData.ts
Purpose: Aggregates petition context data from multiple sources for form initialization.
Signature:
async function getPetitionContextData(
result: PetitionResponse,
slug: string
): Promise<PetitionDataUtilityResult>
Parameters:
result- Petition response from APIslug- Campaign slug for data fetching
Returns:
{
blockNumbering: string;
addJurisdiction: string;
turnInDateRequired: boolean;
oldBlockList: number[] | null;
lastCountyValue: string[] | null;
defaultCountyValue: string | null;
defaultTurnInDate: string | null;
defaultReturnedBy: string | null;
lastTurnInDateValue: string | null;
lastPlaceSigned: string | null;
lastReturnedByValue: string | null;
turInDateBySession: string | null;
}
Logic Flow:
-
Extract Campaign Settings:
blockNumbering- "manual" or "automatic"addJurisdiction- "county" or "none"turnInDateRequired- BooleanreturnedByRequired- Boolean
-
Block List (if manual):
- Calls
getPreviousBlockListOfPetition(slug) - Extracts block numbers from response
- Returns array of used block numbers
- Calls
-
Default Values (if needed):
- If jurisdiction, turn-in date, or returned-by required:
- Calls
getDefaultCountyByUser(slug) - Extracts default county, turn-in date, returned-by
- Calls
- If jurisdiction, turn-in date, or returned-by required:
-
Session Turn-In Date:
- If
turnInDateRequired === true:- Calls
getSessionBasedTurnInDate(slug) - Gets session-specific turn-in date
- Calls
- If
-
Latest Values:
- Calls
getLastestValueByUserInPetitions()with dynamic columns:- Always:
["placeSigned"] - Conditionally:
["turnInDate"],["county"],["returnedBy"]
- Always:
- Extracts most recent values (first in array)
- Handles array responses
- Calls
Conditional Logic:
- Only fetches data needed based on campaign settings
- Optimizes API calls by skipping unnecessary requests
- Handles null/undefined responses gracefully
Example:
// Campaign requires county and turn-in date
// Fetches: defaultCounty, defaultTurnInDate, sessionTurnInDate,
// lastCountyValue, lastTurnInDateValue, lastPlaceSigned
5.2 submitSignatures
Location: src/actions/signatures/submitSignatures.ts
Purpose: Submits signature entries to database with validation and conflict resolution.
Signature:
async function submitSignatures({
searchResults,
signatureState,
campaignSlug,
activePage,
requiredSignatures
}: SubmitSignaturesParams): Promise<{ success?: boolean; error?: string }>
Parameters:
{
searchResults: VoterSearchResultTypeExtended[];
signatureState: PetitionPageResponse;
campaignSlug: string;
activePage: number;
requiredSignatures: number;
}
Returns: Success or error object
Logic Flow:
-
Filter Selected Signatures:
- Filters results where
selected !== null - Only processes entries user has interacted with
- Filters results where
-
Transform Data:
- Maps each result to signature object
- Two transformation paths:
Path A: Selected Signatures (
selected === true):{
firstName: _source.first_name || originalParams.firstName,
lastName: _source.last_name || originalParams.lastName,
middleName: _source.middle_name || null,
street: address_hn + address_st_n || houseNumber || null,
zip: address_zip || zip || null,
voter_id: _source.voter_id || null,
state: address_state || null,
city: address_city || originalParams.city || null,
petition: signatureState.id,
campaign: campaignSlug,
format: signatureState.format.id,
status: person.status || "invalid",
statusDetails: person.statusDetails || "No data",
party: party_code || null,
age: age || null,
gender: gender || null,
signature_id: `${petitionNo}.${activePage}.${searchIndex}`,
unit: address_unit || null,
county: address_county || originalParams.county || null,
circulator: signatureState.circulator.id,
address: address_full || null,
precinct_number: precinct_number || null
}Path B: Unselected Signatures (
selected === false):{
status: person.status,
statusDetails: person.statusDetails,
petition: signatureState.id,
campaign: campaignSlug,
format: signatureState.format.id,
circulator: signatureState.circulator.id,
signature_id: `${petitionNo}.${activePage}.${searchIndex}`,
// If valid with new registration data:
regNo?: newRegistrationData.regNo,
firstName?: newRegistrationData.firstName,
lastName?: newRegistrationData.lastName
} -
Validation:
- Checks if count exceeds
requiredSignatures - Returns error if too many signatures
- Returns error if no signatures to insert
- Checks if count exceeds
-
Database Upsert:
- Uses Supabase
upsert()with conflict resolution - Conflict key:
signature_id,campaign(composite unique constraint) - Updates existing or inserts new
- Uses Supabase
Signature ID Format:
{petitionNo}.{activePage}.{searchIndex}
Example: "123.1.5" (petition 123, page 1, entry 5)
Data Priority:
- Prefers
_sourcedata from search results - Falls back to
originalParamsfrom form entry - Handles missing fields gracefully
Error Handling:
- Returns error object on validation failure
- Returns error object on database error
- Returns success object on completion