Skip to main content

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 API
  • slug - 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:

  1. Extract Campaign Settings:

    • blockNumbering - "manual" or "automatic"
    • addJurisdiction - "county" or "none"
    • turnInDateRequired - Boolean
    • returnedByRequired - Boolean
  2. Block List (if manual):

    • Calls getPreviousBlockListOfPetition(slug)
    • Extracts block numbers from response
    • Returns array of used block numbers
  3. Default Values (if needed):

    • If jurisdiction, turn-in date, or returned-by required:
      • Calls getDefaultCountyByUser(slug)
      • Extracts default county, turn-in date, returned-by
  4. Session Turn-In Date:

    • If turnInDateRequired === true:
      • Calls getSessionBasedTurnInDate(slug)
      • Gets session-specific turn-in date
  5. Latest Values:

    • Calls getLastestValueByUserInPetitions() with dynamic columns:
      • Always: ["placeSigned"]
      • Conditionally: ["turnInDate"], ["county"], ["returnedBy"]
    • Extracts most recent values (first in array)
    • Handles array responses

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:

  1. Filter Selected Signatures:

    • Filters results where selected !== null
    • Only processes entries user has interacted with
  2. 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
    }
  3. Validation:

    • Checks if count exceeds requiredSignatures
    • Returns error if too many signatures
    • Returns error if no signatures to insert
  4. Database Upsert:

    • Uses Supabase upsert() with conflict resolution
    • Conflict key: signature_id,campaign (composite unique constraint)
    • Updates existing or inserts new

Signature ID Format:

{petitionNo}.{activePage}.{searchIndex}
Example: "123.1.5" (petition 123, page 1, entry 5)

Data Priority:

  • Prefers _source data from search results
  • Falls back to originalParams from 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