Skip to main content

Signature Search & Validation

3.1 SignatureResultContextProvider

Location: src/context/signatureResultsContext/signatureResultContext.tsx

Purpose: Manages the complete signature entry workflow including form data, search results, and validation state.

Props:

{
children: React.ReactNode;
jurisdictionType: string | null;
jurisdictionName: string | null;
}

State Management:

  1. Form Data Array:

    • Array of ESFormEntry objects
    • One entry per required signature
    • Initialized based on requiredSignatures
  2. Search Results:

    • Array of VoterSearchResultTypeExtended
    • Mapped to form entries by searchIndex
    • Includes validation status and details
  3. Active Step:

    • "form" - Entry form
    • "validate" - Validation/results view
  4. Page Management:

    • Tracks current page number
    • Calculates total pages from format details
    • Resets form data on page change

Key Logic:

  1. Debounced Search:

    • Uses 500ms debounce on form data
    • Triggers search when entry is complete
    • Prevents excessive API calls
  2. Result Updates:

    • Compares new search params with existing
    • Only searches if values changed
    • Updates or adds results based on searchIndex
  3. Petition Pre-creation:

    • Creates petition on last page
    • Pre-fetches next route
    • Optimizes navigation performance

Dependencies:

  • useESValidation - Search hook
  • usePetitionContext - Petition state
  • useSignatureStep - Step navigation
  • useDebounce - Debounce utility

3.2 updateSearchResults

Location: src/context/signatureResultsContext/processFunc.ts

Purpose: Merges new search results with existing results, handling jurisdiction validation and multiple matches.

Signature:

function updateSearchResults(
newResult: ESSearchResult,
entry: ESFormEntry,
searchResults: VoterSearchResultTypeExtended[],
jurisdictionType: string,
jurisdictionName: string
): VoterSearchResultTypeExtended[]

Parameters:

  • newResult - Raw search results from API
  • entry - Original form entry that triggered search
  • searchResults - Current search results array
  • jurisdictionType - "city" or "county"
  • jurisdictionName - Jurisdiction name to validate against

Returns: Updated search results array

Logic Flow:

  1. Jurisdiction Check:

    • Calls checkJurisdiction to validate all results
    • Marks results as invalid if wrong jurisdiction
  2. Find Existing Entry:

    • Locates result with matching searchIndex
  3. Update First Result:

    • Replaces existing entry with first result
    • Preserves originalParams and searchIndex
    • Sets _type to "results"
  4. Handle Multiple Results:

    • If more than one result, adds additional entries
    • Maintains same searchIndex for all matches
    • Generates unique search_id for each
  5. Return Updated Array:

    • Returns new array with all updates
    • Preserves order by searchIndex

Example:

// Entry with searchIndex: 1
// API returns 2 matches
// Result: 2 entries in array, both with searchIndex: 1
// search_id: "1" and "11"

Notes:

  • Always returns array, even if no results
  • Preserves existing results not being updated
  • Handles empty result sets gracefully

3.3 checkJurisdiction

Location: src/context/signatureResultsContext/processFunc.ts

Purpose: Validates search results against campaign jurisdiction requirements.

Signature:

function checkJurisdiction({
newResult,
jurisdictionType,
jurisdictionName
}: {
newResult: ESSearchResult;
jurisdictionType: string;
jurisdictionName: string;
}): ESSearchResult

Parameters:

  • newResult - Search results from API
  • jurisdictionType - "city" or "county"
  • jurisdictionName - Required jurisdiction name

Returns: Results with jurisdiction validation applied

Logic:

  1. Skip Invalid Results:

    • Results already marked "invalid" are unchanged
    • Results marked as "not found" are unchanged
  2. City Jurisdiction:

    • Compares address_city_jurisdiction with jurisdictionName
    • Marks as invalid if mismatch
  3. County Jurisdiction:

    • Compares address_county with jurisdictionName
    • Marks as invalid if mismatch
  4. Status Update:

    • Sets status: "invalid" if wrong jurisdiction
    • Sets statusDetails: "Wrong Jurisdiction"
    • Resets selected to null

Data Paths:

  • City: result._source.v_hh_address.address_city_jurisdiction
  • County: result._source.v_hh_address.address_county

Notes:

  • Case-insensitive comparison (via toLocaleLowerCase())
  • Preserves other status information
  • Returns all results, not just valid ones

3.4 isWrongJurisdiction

Location: src/context/signatureResultsContext/processFunc.ts

Purpose: Helper function to check if a single result is in the wrong jurisdiction.

Signature:

function isWrongJurisdiction({
result,
jurisdictionType,
jurisdictionName
}: {
result: VoterSearchResultTypeExtended;
jurisdictionType: string;
jurisdictionName: string;
}): boolean

Returns: true if wrong jurisdiction, false otherwise

Logic:

  • City: Checks address_city_jurisdiction
  • County: Checks address_county
  • Returns false for unknown jurisdiction types

3.5 processFilledEntries

Location: src/context/signatureResultsContext/processFilledEntries.ts

Purpose: Processes form entries to identify completed entries and handle invalid entries.

Signature:

function processFilledEntries({
formDataArray,
searchResults,
setSearchResults
}: ProcessEntriesParams): ProcessEntriesReturn

Parameters:

  • formDataArray - Array of form entries
  • searchResults - Current search results
  • setSearchResults - State setter function

Returns:

{
filledEntries: ESFormEntry[];
updatedSearchResults: VoterSearchResultTypeExtended[];
}

Logic Flow:

  1. Handle Invalid Entries (noSearch category):

    • Creates invalid result object
    • Adds to search results if not exists
    • Updates existing result if found
    • Returns false to exclude from filled entries
  2. Handle Valid but Incomplete Entries:

    • If status === "valid" but entry incomplete
    • Removes associated search results
    • Returns false to exclude
  3. Filter Complete Entries:

    • Uses isEntryComplete() to validate
    • Only includes entries with searchIndex !== 0
    • Returns array of entries ready for search

Invalid Entry Structure:

{
_index: "voter",
_id: searchIndex.toString(),
_type: "noSearch",
searchIndex: entry.searchIndex,
status: "invalid",
statusDetails: entry.statusDetails,
selected: false,
search_id: `${searchIndex}`,
originalParams: entry,
newRegistrationData?: {...} // If regNo exists
}

Notes:

  • Mutates searchResults via setSearchResults
  • Returns copy of updated results
  • Handles edge cases for incomplete valid entries

3.6 searchMultipleAndReturn

Location: src/hooks/add-signatures/useESValidation.tsx

Purpose: Performs multiple voter searches sequentially and aggregates results with duplicate checking.

Signature:

async function searchMultipleAndReturn(
paramsArray: SearchParams[],
campaignId: string
): Promise<ESSearchResult>

Parameters:

  • paramsArray - Array of search parameters (one per form entry)
  • campaignId - Campaign ID for duplicate checking

Returns: Combined search results with validation status

Logic Flow:

  1. Initialize Combined Results:

    {
    persons: [],
    hitsCount: 0,
    totalHits: 0
    }
  2. Loop Through Parameters:

    • Makes API call for each parameter
    • Handles errors per search
  3. Handle No Results:

    • Creates placeholder "not found" result
    • Sets status: "bad", statusDetails: "no match"
    • Sets isNotFoundResult: true
    • Continues to next search
  4. Process Valid Results:

    • For each person in results:
      • Checks duplicate via checkVoterDuplicate()
      • Sets status: "invalid" if duplicate, "valid" otherwise
      • Sets statusDetails: "duplicate" or "matched"
      • Adds searchIndex and originalParams
  5. Aggregate Results:

    • Merges persons arrays
    • Sums hitsCount and totalHits
    • Preserves error information if present
  6. Error Handling:

    • Throws error if API call fails
    • Includes error message from API response

Duplicate Checking:

  • Calls /api/duplicate endpoint
  • Checks if voter_id exists in campaign signatures
  • Returns true if duplicate found

Result Structure:

{
persons: [
{
...voterData,
status: "valid" | "invalid",
statusDetails: "matched" | "duplicate",
searchIndex: number,
originalParams: SearchParams,
isNotFoundResult: boolean
}
],
hitsCount: number,
totalHits: number,
error?: string,
errorCode?: number
}

Performance:

  • Sequential execution (not parallel)
  • Each search waits for previous to complete
  • Duplicate checks run in parallel via Promise.all()

3.7 checkVoterDuplicate

Location: src/hooks/add-signatures/useESValidation.tsx

Purpose: Checks if a voter has already been added to a campaign.

Signature:

async function checkVoterDuplicate(
voterId: string,
campaignId: string
): Promise<boolean>

Parameters:

  • voterId - Voter ID from search result
  • campaignId - Campaign ID to check

Returns: true if duplicate, false otherwise

Implementation:

  • POSTs to /api/duplicate endpoint
  • Sends { campaignId, voterId }
  • Returns data.exists from response

Error Handling:

  • Returns false on error (assumes not duplicate)
  • Logs error to console

3.8 isEntryComplete

Location: src/lib/utils.ts

Purpose: Validates that a form entry has all required fields filled.

Signature:

function isEntryComplete(entry: ESFormEntry): boolean

Validation Rules:

  • firstName - Must be non-empty after trim
  • lastName - Must be non-empty after trim
  • houseNumber - Must be non-empty after trim
  • zip - Must be non-empty after trim AND exactly 5 characters

Returns: true if all validations pass, false otherwise

Example:

isEntryComplete({
firstName: "John",
lastName: "Doe",
houseNumber: "123",
zip: "12345"
}); // Returns: true

isEntryComplete({
firstName: "John",
lastName: "",
houseNumber: "123",
zip: "1234" // Only 4 digits
}); // Returns: false

3.9 isEntryEmpty

Location: src/lib/utils.ts

Purpose: Checks if a form entry is completely empty.

Signature:

function isEntryEmpty(entry: ESFormEntry): boolean

Returns: true if all fields are empty after trim, false otherwise

Use Case: Determines if entry should be ignored or reset