Pass 2 W2: correcoes de fronteira lib/utilities e client boundary
- helpers puros movidos utilities->lib: slug, phone, wordStartFilter, voteEstimate (9 inversoes lib->utilities zeradas) - chaves de consent em src/lib/campaignConsentKeys.ts (client-safe); campaignInvite/campaignConsent re-exportam; onda0ConsentTexts referencia as constantes - 'server-only' em 21 loaders Payload/cache (CLI segue com --conditions=react-server) - ciclo de tipos supporterListFilters <-> supporterUi quebrado (SupporterListFilterInput estrutural; sqlFilters idem) - votePledgeData dividido: votePledgeViews (puro, client-safe) + votePledgeData (loaders, server-only) - padrao contract-module - predicados de role client-safe em src/lib/campaignRoles.ts; access/shared deriva deles (fonte unica); nav.ts para de importar o barrel campaignAccess (casts 'as CampaignUser' eliminados)
This commit is contained in:
parent
23e1f1e83d
commit
cf6ae6c47c
111 changed files with 397 additions and 321 deletions
|
|
@ -10,7 +10,7 @@ import {
|
|||
} from '@/lib/formData'
|
||||
import { supporterCreateSchema } from '@/lib/schemas/supporter'
|
||||
import { mapCampaignFormActionError } from '@/utilities/campaignFormActionError'
|
||||
import { sanitizeBrazilianPhoneInput } from '@/utilities/phone'
|
||||
import { sanitizeBrazilianPhoneInput } from '@/lib/phone'
|
||||
|
||||
export type SupporterFormState = {
|
||||
status?: 'success'
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import { isPlanilhaPlaceholderEmail } from '@/lib/schemas/advisor'
|
|||
import { loadAdvisorDetail } from '@/utilities/advisorData'
|
||||
import { isCampaignUnrestricted } from '@/utilities/campaignAccess'
|
||||
import { getCampaignUser } from '@/utilities/campaignAuth'
|
||||
import { formatBrazilianPhoneInput } from '@/utilities/phone'
|
||||
import { formatBrazilianPhoneInput } from '@/lib/phone'
|
||||
import {
|
||||
sendAdvisorPasswordResetFormAction,
|
||||
updateAdvisorProfileFormAction,
|
||||
|
|
|
|||
|
|
@ -50,14 +50,9 @@ import {
|
|||
parseMunicipalityUpdateFeedParams,
|
||||
} from '@/utilities/municipalityUpdatePageData'
|
||||
import { loadAdvisorSummaries } from '@/utilities/municipalityViewModels'
|
||||
import type { VoteEstimateScenarioViewModel } from '@/utilities/voteEstimate'
|
||||
import {
|
||||
aggregateMunicipalityPledgesFromRows,
|
||||
loadMunicipalityPledges,
|
||||
toMunicipalityPledgeCoverageView,
|
||||
type MunicipalityPledgeAggregate,
|
||||
type MunicipalityPledgeCoverageView,
|
||||
} from '@/utilities/votePledgeData'
|
||||
import type { VoteEstimateScenarioViewModel } from '@/lib/voteEstimate'
|
||||
import { loadMunicipalityPledges } from '@/utilities/votePledgeData'
|
||||
import { aggregateMunicipalityPledgesFromRows, toMunicipalityPledgeCoverageView, type MunicipalityPledgeAggregate, type MunicipalityPledgeCoverageView } from '@/utilities/votePledgeViews'
|
||||
import { declareVotesFormAction, estimateVotesFormAction } from './pledgeFormActions'
|
||||
import { createMunicipalityUpdateFormAction } from './updateFormActions'
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
CAMPAIGN_SESSION_EXPIRED_MESSAGE,
|
||||
mapCampaignFormActionError,
|
||||
} from '@/utilities/campaignFormActionError'
|
||||
import { toVoteEstimateScenarioViewModel } from '@/utilities/voteEstimate'
|
||||
import { toVoteEstimateScenarioViewModel } from '@/lib/voteEstimate'
|
||||
|
||||
import type { MunicipalityListExpectedVotesResponse } from './types'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { VoteEstimateScenarioViewModel } from '@/utilities/voteEstimate'
|
||||
import type { VoteEstimateScenarioViewModel } from '@/lib/voteEstimate'
|
||||
|
||||
export type MunicipalityListExpectedVotesResponse =
|
||||
| {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import {
|
|||
parseActionPlanCreateFormData,
|
||||
parseActionPlanUpdateFormData,
|
||||
} from '@/utilities/actionPlanFormData'
|
||||
import { slugify } from '@/utilities/slug'
|
||||
import { slugify } from '@/lib/slug'
|
||||
|
||||
const getActionError = (error: unknown): ActionPlanFormState => {
|
||||
if (
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import { mapCampaignFormActionError } from '@/utilities/campaignFormActionError'
|
|||
import { acquireContactPhoneLocks } from '@/utilities/contactPhoneInvariant'
|
||||
import type { PayloadTransactionRequest } from '@/utilities/payloadTransaction'
|
||||
import { withPayloadTransaction } from '@/utilities/payloadTransaction'
|
||||
import { sanitizeBrazilianPhoneInput } from '@/utilities/phone'
|
||||
import { sanitizeBrazilianPhoneInput } from '@/lib/phone'
|
||||
import { isUniqueSupporterConflict } from '@/utilities/supporterErrors'
|
||||
|
||||
export type LeaderSupporterFormState = {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import { acquireTextAdvisoryLocks } from '@/utilities/postgresTransactionLocks'
|
|||
import {
|
||||
normalizeVoteEstimateOnSave,
|
||||
toVoteEstimateScenarioViewModel,
|
||||
} from '@/utilities/voteEstimate'
|
||||
} from '@/lib/voteEstimate'
|
||||
|
||||
const getFreshStaffActor = (
|
||||
payload: Payload,
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import {
|
|||
mapCampaignFormActionError,
|
||||
type CampaignFormActionState,
|
||||
} from '@/utilities/campaignFormActionError'
|
||||
import { normalizeBrazilianPhone } from '@/utilities/phone'
|
||||
import { normalizeBrazilianPhone } from '@/lib/phone'
|
||||
|
||||
export const requestCampaignPasswordReset = async (
|
||||
input: { email: string },
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import {
|
|||
import { acquireContactPhoneLocks } from '@/utilities/contactPhoneInvariant'
|
||||
import type { PayloadTransactionRequest } from '@/utilities/payloadTransaction'
|
||||
import { withPayloadTransaction } from '@/utilities/payloadTransaction'
|
||||
import { normalizeBrazilianPhone } from '@/utilities/phone'
|
||||
import { normalizeBrazilianPhone } from '@/lib/phone'
|
||||
import { relationshipId } from '@/utilities/relationship'
|
||||
import {
|
||||
isPreviewErrorRow,
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import { relationshipId, requireRelationshipId } from '@/utilities/relationship'
|
|||
import {
|
||||
normalizeVoteEstimateOnSave,
|
||||
toVoteEstimateScenarioViewModel,
|
||||
} from '@/utilities/voteEstimate'
|
||||
} from '@/lib/voteEstimate'
|
||||
|
||||
const STAFF_DECLARE_REQUIRED = 'Somente a coordenação e a assessoria registram votos declarados.'
|
||||
const MUNICIPALITY_NOT_LINKED =
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import {
|
|||
eligibleCampaignStaffWhere,
|
||||
} from '@/utilities/campaignAccess'
|
||||
import { relationshipId } from '@/utilities/relationship'
|
||||
import { slugify } from '@/utilities/slug'
|
||||
import { slugify } from '@/lib/slug'
|
||||
|
||||
const trimmedText = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import {
|
|||
isCampaignUnrestricted,
|
||||
} from '@/utilities/campaignAccess'
|
||||
import { relationshipId } from '@/utilities/relationship'
|
||||
import { slugify } from '@/utilities/slug'
|
||||
import { slugify } from '@/lib/slug'
|
||||
|
||||
const trimmedText = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {
|
|||
canUpdateCampaignUserPhone,
|
||||
} from '@/utilities/campaignAccess'
|
||||
import { buildCampaignPasswordResetUrl } from '@/utilities/campaignPasswordReset'
|
||||
import { normalizeBrazilianPhone } from '@/utilities/phone'
|
||||
import { normalizeBrazilianPhone } from '@/lib/phone'
|
||||
import {
|
||||
APIError,
|
||||
type CollectionAfterReadHook,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
canManageContacts,
|
||||
canReadContacts,
|
||||
} from '@/utilities/campaignAccess'
|
||||
import { normalizeBrazilianPhone } from '@/utilities/phone'
|
||||
import { normalizeBrazilianPhone } from '@/lib/phone'
|
||||
import type { CollectionBeforeChangeHook, CollectionConfig } from 'payload'
|
||||
import { APIError } from 'payload'
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import {
|
|||
getVoteEstimateOrderViolation,
|
||||
VOTE_ESTIMATE_ORDER_ERROR_MESSAGE,
|
||||
type VoteEstimateScenarioFields,
|
||||
} from '@/utilities/voteEstimate'
|
||||
} from '@/lib/voteEstimate'
|
||||
import {
|
||||
voteEstimateScenarioGroupAccess,
|
||||
voteEstimateScenarioGroupFields,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
canReadOrganization,
|
||||
canSetCampaignSystemField,
|
||||
} from '@/utilities/campaignAccess'
|
||||
import { slugify } from '@/utilities/slug'
|
||||
import { slugify } from '@/lib/slug'
|
||||
|
||||
const trimmedText = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { payloadAdminOnly } from '@/utilities/campaignAccess'
|
||||
import { revalidateDocumentById, revalidatePostsListing } from '@/utilities/documents'
|
||||
import { slugify } from '@/utilities/slug'
|
||||
import { slugify } from '@/lib/slug'
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
const slug = 'post'
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
canReadStateDeputy,
|
||||
canSetCampaignSystemField,
|
||||
} from '@/utilities/campaignAccess'
|
||||
import { slugify } from '@/utilities/slug'
|
||||
import { slugify } from '@/lib/slug'
|
||||
|
||||
const trimmedText = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { payloadAdminOnly } from '@/utilities/campaignAccess'
|
||||
import { revalidateDocumentById, revalidatePostsListing } from '@/utilities/documents'
|
||||
import { slugify } from '@/utilities/slug'
|
||||
import { slugify } from '@/lib/slug'
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
const slug = 'tag'
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import {
|
|||
type VoteEstimateScenarioFields,
|
||||
getVoteEstimateOrderViolation,
|
||||
VOTE_ESTIMATE_ORDER_ERROR_MESSAGE,
|
||||
} from '@/utilities/voteEstimate'
|
||||
} from '@/lib/voteEstimate'
|
||||
import {
|
||||
voteEstimateScenarioGroupAccess,
|
||||
voteEstimateScenarioGroupFields,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { ComponentProps } from 'react'
|
||||
|
||||
import { formatBrazilianPhoneInput, sanitizeBrazilianPhoneInput } from '@/utilities/phone'
|
||||
import { formatBrazilianPhoneInput, sanitizeBrazilianPhoneInput } from '@/lib/phone'
|
||||
|
||||
import { FormInput } from './FormInput'
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {
|
|||
planilhaPlaceholderEmailForAdvisor,
|
||||
} from '@/lib/schemas/advisor'
|
||||
import type { CampaignFormActionState } from '@/utilities/campaignFormActionError'
|
||||
import { formatBrazilianPhoneInput, sanitizeBrazilianPhoneInput } from '@/utilities/phone'
|
||||
import { formatBrazilianPhoneInput, sanitizeBrazilianPhoneInput } from '@/lib/phone'
|
||||
|
||||
const SAVE_DEBOUNCE_MS = 500
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ import {
|
|||
normalizeBrazilianPhone,
|
||||
sanitizeBrazilianPhoneInput,
|
||||
buildWhatsAppUrl,
|
||||
} from '@/utilities/phone'
|
||||
} from '@/lib/phone'
|
||||
|
||||
type MunicipalityRef = { id: number; name: string; slug: string }
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import {
|
|||
import {
|
||||
formatVoteEstimateEndpointsLabel,
|
||||
voteEstimateScenarioLabels,
|
||||
} from '@/utilities/voteEstimate'
|
||||
} from '@/lib/voteEstimate'
|
||||
|
||||
const dateTimeFormatter = new Intl.DateTimeFormat('pt-BR', {
|
||||
dateStyle: 'short',
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import { Spinner } from '@/components/ui/Spinner'
|
|||
import type { CampaignFormActionState } from '@/utilities/campaignFormActionError'
|
||||
import { fieldError } from '@/utilities/campaignFormFields'
|
||||
import { campaignRoleLabels, type CampaignUserShellView } from '@/utilities/campaignUserProfile'
|
||||
import { formatBrazilianPhoneDisplay } from '@/utilities/phone'
|
||||
import { formatBrazilianPhoneDisplay } from '@/lib/phone'
|
||||
|
||||
type CampaignProfileSettingsProps = {
|
||||
user: CampaignUserShellView & {
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import { Input } from '@/components/ui/input'
|
|||
import { NativeSelect, NativeSelectOption } from '@/components/ui/native-select'
|
||||
import { Spinner } from '@/components/ui/Spinner'
|
||||
import { errorProps as buildErrorProps, fieldError } from '@/utilities/campaignFormFields'
|
||||
import { formatBrazilianPhoneInput, sanitizeBrazilianPhoneInput } from '@/utilities/phone'
|
||||
import { formatBrazilianPhoneInput, sanitizeBrazilianPhoneInput } from '@/lib/phone'
|
||||
import { municipalityComboboxOptions } from '@/utilities/territoryComboboxOptions'
|
||||
|
||||
const errorProps = (fieldErrors: Record<string, string[]> | undefined, field: string) =>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import {
|
|||
TableRow,
|
||||
} from '@/components/ui/Table'
|
||||
import type { LeaderContactListItem } from '@/utilities/leaderContactsPageData'
|
||||
import { formatBrazilianPhoneInput } from '@/utilities/phone'
|
||||
import { formatBrazilianPhoneInput } from '@/lib/phone'
|
||||
|
||||
const dateFormatter = new Intl.DateTimeFormat('pt-BR', {
|
||||
dateStyle: 'short',
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import { createContext, useContext, useMemo, useState, type ReactNode } from 'react'
|
||||
|
||||
import { DEFAULT_VOTE_ESTIMATE_SCENARIO, type VoteEstimateScenario } from '@/utilities/voteEstimate'
|
||||
import { DEFAULT_VOTE_ESTIMATE_SCENARIO, type VoteEstimateScenario } from '@/lib/voteEstimate'
|
||||
|
||||
type MunicipalityEstimateScenarioContextValue = {
|
||||
scenario: VoteEstimateScenario
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ import {
|
|||
type MunicipalityGoalCoverage,
|
||||
} from '@/utilities/goalCoverage'
|
||||
import type { MunicipalityPotential, RollOff } from '@/utilities/municipalityPotential'
|
||||
import type { VoteEstimateScenarioViewModel } from '@/utilities/voteEstimate'
|
||||
import type { MunicipalityPledgeCoverageView } from '@/utilities/votePledgeData'
|
||||
import type { VoteEstimateScenarioViewModel } from '@/lib/voteEstimate'
|
||||
import type { MunicipalityPledgeCoverageView } from '@/utilities/votePledgeViews'
|
||||
|
||||
/**
|
||||
* One `dt`/`dd` pair in the diagnostic grid, with a hover/focus/tap tooltip
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ import {
|
|||
type MunicipalityMultiFilterParam,
|
||||
} from '@/utilities/municipalityListFilters'
|
||||
import { type MunicipalityListState } from '@/utilities/municipalityListUrl'
|
||||
import { normalizeSearchPhrase } from '@/utilities/wordStartFilter'
|
||||
import { normalizeSearchPhrase } from '@/lib/wordStartFilter'
|
||||
|
||||
type MunicipalityHeaderFilterProps = {
|
||||
state: MunicipalityListState
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { Badge } from '@/components/ui/Badge'
|
|||
import { Button } from '@/components/ui/button'
|
||||
import type { CampaignFormActionState } from '@/utilities/campaignFormActionError'
|
||||
import type { LeadershipRowViewModel } from '@/utilities/leadershipData'
|
||||
import type { StaffPledgeRow } from '@/utilities/votePledgeData'
|
||||
import type { StaffPledgeRow } from '@/utilities/votePledgeViews'
|
||||
|
||||
type MunicipalityLeadershipsPanelProps = {
|
||||
municipalityID: number
|
||||
|
|
|
|||
|
|
@ -56,8 +56,8 @@ import type {
|
|||
import {
|
||||
DEFAULT_VOTE_ESTIMATE_SCENARIO,
|
||||
voteEstimateScenarioLabels,
|
||||
} from '@/utilities/voteEstimate'
|
||||
import { toMunicipalityPledgeCoverageView } from '@/utilities/votePledgeData'
|
||||
} from '@/lib/voteEstimate'
|
||||
import { toMunicipalityPledgeCoverageView } from '@/utilities/votePledgeViews'
|
||||
|
||||
const dateFormatter = new Intl.DateTimeFormat('pt-BR')
|
||||
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ import {
|
|||
DEFAULT_VOTE_ESTIMATE_SCENARIO,
|
||||
voteEstimatesEqual,
|
||||
type VoteEstimateScenarioViewModel,
|
||||
} from '@/utilities/voteEstimate'
|
||||
import type { MunicipalityPledgeCoverageView } from '@/utilities/votePledgeData'
|
||||
} from '@/lib/voteEstimate'
|
||||
import type { MunicipalityPledgeCoverageView } from '@/utilities/votePledgeViews'
|
||||
|
||||
const AUTOSAVE_MS = 600
|
||||
const EXPECTED_VOTES_ENDPOINT = '/campanha/municipios/expected-votes'
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {
|
|||
DEFAULT_VOTE_ESTIMATE_SCENARIO,
|
||||
voteEstimateScenarioLabels,
|
||||
type VoteEstimateScenario,
|
||||
} from '@/utilities/voteEstimate'
|
||||
} from '@/lib/voteEstimate'
|
||||
|
||||
/**
|
||||
* E8 "conta da cadeira" list cell: reads the shared scenario picker (same
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import {
|
|||
formatVoteEstimateEndpointsLabel,
|
||||
hasAnyVoteEstimate,
|
||||
voteEstimateScenarioLabels,
|
||||
} from '@/utilities/voteEstimate'
|
||||
} from '@/lib/voteEstimate'
|
||||
|
||||
const SCENARIO_FLASH_MS = 420
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ import {
|
|||
DEFAULT_VOTE_ESTIMATE_SCENARIO,
|
||||
voteEstimateScenarioLabels,
|
||||
type VoteEstimateScenario,
|
||||
} from '@/utilities/voteEstimate'
|
||||
} from '@/lib/voteEstimate'
|
||||
|
||||
type MunicipalityMapPanelProps = {
|
||||
bundle: MunicipalityMapBundle
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ import {
|
|||
effectivePledgeVotesForScenario,
|
||||
formatVoteEstimateRange,
|
||||
getVoteEstimateForScenario,
|
||||
} from '@/utilities/voteEstimate'
|
||||
import { type StaffPledgeRow } from '@/utilities/votePledgeData'
|
||||
import { hasAnyEstimate } from '@/utilities/voteEstimate'
|
||||
} from '@/lib/voteEstimate'
|
||||
import { type StaffPledgeRow } from '@/utilities/votePledgeViews'
|
||||
import { hasAnyEstimate } from '@/lib/voteEstimate'
|
||||
|
||||
const voteFormatter = new Intl.NumberFormat('pt-BR')
|
||||
const dateFormatter = new Intl.DateTimeFormat('pt-BR')
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { Input } from '@/components/ui/input'
|
|||
import { Spinner } from '@/components/ui/Spinner'
|
||||
import type { CampaignFormActionState } from '@/utilities/campaignFormActionError'
|
||||
import { fieldError } from '@/utilities/campaignFormFields'
|
||||
import type { VoteEstimateScenarioViewModel } from '@/utilities/voteEstimate'
|
||||
import type { VoteEstimateScenarioViewModel } from '@/lib/voteEstimate'
|
||||
|
||||
type PledgeEstimateFormProps = {
|
||||
pledgeID: number
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ import {
|
|||
hasAnyVoteEstimate,
|
||||
type VoteEstimateScenario,
|
||||
type VoteEstimateScenarioViewModel,
|
||||
} from '@/utilities/voteEstimate'
|
||||
import type { MunicipalityPledgeCoverageView } from '@/utilities/votePledgeData'
|
||||
} from '@/lib/voteEstimate'
|
||||
import type { MunicipalityPledgeCoverageView } from '@/utilities/votePledgeViews'
|
||||
|
||||
const voteFormatter = new Intl.NumberFormat('pt-BR')
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {
|
|||
ComboboxList,
|
||||
} from '@/components/ui/combobox'
|
||||
import type { StrictComboboxOption } from '@/utilities/territoryComboboxOptions'
|
||||
import { matchesAtWordStart, normalizeSearchPhrase } from '@/utilities/wordStartFilter'
|
||||
import { matchesAtWordStart, normalizeSearchPhrase } from '@/lib/wordStartFilter'
|
||||
|
||||
type StrictComboboxProps = {
|
||||
id: string
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import { Spinner } from '@/components/ui/Spinner'
|
|||
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/ToggleGroup'
|
||||
import type { SupporterVoteIntention } from '@/lib/schemas/supporter'
|
||||
import { fieldError, errorProps as buildErrorProps } from '@/utilities/campaignFormFields'
|
||||
import { formatBrazilianPhoneInput, sanitizeBrazilianPhoneInput } from '@/utilities/phone'
|
||||
import { formatBrazilianPhoneInput, sanitizeBrazilianPhoneInput } from '@/lib/phone'
|
||||
import { supporterVoteIntentionLabels } from '@/utilities/supporterUi'
|
||||
import { municipalityComboboxOptions } from '@/utilities/territoryComboboxOptions'
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
import { Badge } from '@/components/ui/Badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { formatBrazilianPhoneInput } from '@/utilities/phone'
|
||||
import { formatBrazilianPhoneInput } from '@/lib/phone'
|
||||
import { supporterVoteIntentionLabels } from '@/utilities/supporterUi'
|
||||
import type { SupporterListItemViewModel } from '@/utilities/supporterViewModels'
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { toast } from 'sonner'
|
|||
import { Alert, AlertDescription } from '@/components/ui/Alert'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { buildWhatsAppUrl } from '@/utilities/phone'
|
||||
import { buildWhatsAppUrl } from '@/lib/phone'
|
||||
|
||||
export const SupporterShareKit = ({
|
||||
supporterName,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
VOTE_ESTIMATE_SCENARIOS,
|
||||
voteEstimateScenarioLabels,
|
||||
type VoteEstimateScenario,
|
||||
} from '@/utilities/voteEstimate'
|
||||
} from '@/lib/voteEstimate'
|
||||
|
||||
export const VOTE_ESTIMATE_SCENARIO_OVERVIEW_HINT =
|
||||
'Troca o total desta visão geral e os votos na lista. No mapa, a cor só muda com Ano 2026.'
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import {
|
|||
voteEstimatesEqual,
|
||||
type VoteEstimateScenario,
|
||||
type VoteEstimateScenarioViewModel,
|
||||
} from '@/utilities/voteEstimate'
|
||||
} from '@/lib/voteEstimate'
|
||||
|
||||
const scenarioFieldName = (
|
||||
fieldPrefix: 'estimatedVotes' | 'expectedVotes',
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {
|
|||
voteEstimateScenarioLabels,
|
||||
type VoteEstimateScenario,
|
||||
type VoteEstimateScenarioViewModel,
|
||||
} from '@/utilities/voteEstimate'
|
||||
} from '@/lib/voteEstimate'
|
||||
|
||||
const voteFormatter = new Intl.NumberFormat('pt-BR')
|
||||
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ import {
|
|||
type LucideIcon,
|
||||
} from 'lucide-react'
|
||||
|
||||
import { isStaffCampaignRole, isUnrestrictedCampaignRole } from '@/lib/campaignRoles'
|
||||
import type { CampaignUser } from '@/payload-types'
|
||||
import { isCampaignStaff, isCampaignUnrestricted } from '@/utilities/campaignAccess'
|
||||
import { canAccessSupporterArea } from '@/utilities/supporterUi'
|
||||
|
||||
export type CampaignNavItem = {
|
||||
|
|
@ -57,13 +57,13 @@ export const getCampaignNav = (role: CampaignUser['role']): CampaignNavItem[] =>
|
|||
|
||||
return staffNav.filter((item) => {
|
||||
if (item.href === '/campanha/apoiadores') return canAccessSupporterArea(role)
|
||||
if (item.href === '/campanha/assessores') return isCampaignUnrestrictedRole(role)
|
||||
if (item.href === '/campanha/assessores') return isUnrestrictedCampaignRole(role)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
export const getCampaignSecondaryNav = (role: CampaignUser['role']): CampaignNavItem[] =>
|
||||
isCampaignStaffRole(role) ? staffSecondaryNav : []
|
||||
isStaffCampaignRole(role) ? staffSecondaryNav : []
|
||||
|
||||
/**
|
||||
* Compact set for the mobile bottom bar (max 5 items with a home slot).
|
||||
|
|
@ -88,9 +88,3 @@ export const isCampaignNavActive = (pathname: string, href: string): boolean =>
|
|||
}
|
||||
return pathname === href || pathname.startsWith(`${href}/`)
|
||||
}
|
||||
|
||||
export const isCampaignStaffRole = (role: CampaignUser['role']): boolean =>
|
||||
isCampaignStaff({ collection: 'campaignUser', role } as CampaignUser)
|
||||
|
||||
const isCampaignUnrestrictedRole = (role: CampaignUser['role']): boolean =>
|
||||
isCampaignUnrestricted({ collection: 'campaignUser', role } as CampaignUser)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
type MunicipalityCatalogEntry,
|
||||
} from '@/lib/municipalityCatalog'
|
||||
import { citiesForTseZone } from '@/lib/bahiaTseZones'
|
||||
import { matchesAtWordStart, normalizeSearchPhrase } from '@/utilities/wordStartFilter'
|
||||
import { matchesAtWordStart, normalizeSearchPhrase } from '@/lib/wordStartFilter'
|
||||
|
||||
export type AdvisorMunicipalityIndexEntry = {
|
||||
id: number
|
||||
|
|
|
|||
10
src/lib/campaignConsentKeys.ts
Normal file
10
src/lib/campaignConsentKeys.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
/**
|
||||
* Stable LGPD consent keys the app resolves at runtime (fail-closed: the flows
|
||||
* refuse to run while the admin has not created the Consent row with the exact
|
||||
* key). Client-safe constants — the resolution logic lives in
|
||||
* `src/utilities/campaignConsent.ts` (server-only).
|
||||
*/
|
||||
export const CAMPAIGN_INVITE_CONSENT_KEY = 'lideranca-autopreenchimento'
|
||||
|
||||
export const SUPPORTER_REGISTRATION_CONSENT_KEY = 'apoiador-cadastro'
|
||||
export const SUPPORTER_VOTE_INTENTION_CONSENT_KEY = 'apoiador-intencao-voto'
|
||||
18
src/lib/campaignRoles.ts
Normal file
18
src/lib/campaignRoles.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import type { CampaignUser } from '@/payload-types'
|
||||
|
||||
/**
|
||||
* Client-safe campaign ROLE predicates — the single source for what each role
|
||||
* label means. Server-side actor checks (`isCampaignStaff(user)` etc.) derive
|
||||
* from these in `src/utilities/access/shared.ts`; client components (sidebar,
|
||||
* nav) use the role-level predicates directly instead of importing the access
|
||||
* barrel.
|
||||
*/
|
||||
export type CampaignRole = CampaignUser['role']
|
||||
|
||||
/** Staff = coordinator, advisor, or candidate. Leaders are not staff. */
|
||||
export const isStaffCampaignRole = (role: CampaignRole): boolean =>
|
||||
role === 'coordinator' || role === 'advisor' || role === 'candidate'
|
||||
|
||||
/** Coordinator or candidate — unrestricted scope (all municipalities, decisions). */
|
||||
export const isUnrestrictedCampaignRole = (role: CampaignRole): boolean =>
|
||||
role === 'coordinator' || role === 'candidate'
|
||||
|
|
@ -2,7 +2,7 @@ import 'server-only'
|
|||
|
||||
import { z, type ZodError } from 'zod'
|
||||
|
||||
import type { VoteEstimateScenarioFields } from '@/utilities/voteEstimate'
|
||||
import type { VoteEstimateScenarioFields } from '@/lib/voteEstimate'
|
||||
|
||||
export class FormDataBoundaryError extends Error {
|
||||
field: string
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import {
|
|||
} from '@/lib/bahiaTerritories'
|
||||
import { bahiaTseCityCodes } from '@/lib/bahiaTseCityCodes'
|
||||
import { tseZonesForCity } from '@/lib/bahiaTseZones'
|
||||
import { slugify } from '@/utilities/slug'
|
||||
import { slugify } from '@/lib/slug'
|
||||
|
||||
export type MunicipalityKind = 'municipio' | 'zona'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
import {
|
||||
CAMPAIGN_INVITE_CONSENT_KEY,
|
||||
SUPPORTER_REGISTRATION_CONSENT_KEY,
|
||||
SUPPORTER_VOTE_INTENTION_CONSENT_KEY,
|
||||
} from '@/lib/campaignConsentKeys'
|
||||
import type { Consent } from '@/payload-types'
|
||||
import { CAMPAIGN_INVITE_CONSENT_KEY } from '@/utilities/campaignInvite'
|
||||
|
||||
export const ONDA0_PROVISIONAL_BANNER =
|
||||
'Texto provisório para MVP e testes internos da plataforma Teqo (campanha Jorge Solla). Não constitui parecer jurídico. Deve ser substituído por texto aprovado pela assessoria jurídica eleitoral antes de coleta de dados de titulares reais em produção.'
|
||||
|
||||
export const ONDA0_CONSENT_KEYS = {
|
||||
leadershipAutofill: CAMPAIGN_INVITE_CONSENT_KEY,
|
||||
supporterRegistration: 'apoiador-cadastro',
|
||||
supporterVoteIntention: 'apoiador-intencao-voto',
|
||||
supporterRegistration: SUPPORTER_REGISTRATION_CONSENT_KEY,
|
||||
supporterVoteIntention: SUPPORTER_VOTE_INTENTION_CONSENT_KEY,
|
||||
campaignPush: 'campanha-notificacoes-push',
|
||||
} as const
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
* Bom → optimistic, Regular → central (média), Mínimo → pessimistic.
|
||||
*/
|
||||
|
||||
import type { VoteEstimateScenarioFields } from '@/utilities/voteEstimate'
|
||||
import type { VoteEstimateScenarioFields } from '@/lib/voteEstimate'
|
||||
|
||||
/** Parsed sheet triple — all three scenarios present (same keys as expectedVotes). */
|
||||
export type ProjectionVoteEstimates = Required<
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { normalizeBrazilianPhone } from '@/utilities/phone'
|
||||
import { normalizeBrazilianPhone } from '@/lib/phone'
|
||||
import { z } from 'zod'
|
||||
|
||||
const campaignIdentifierSchema = z
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { z } from 'zod'
|
||||
|
||||
import { normalizeBrazilianPhone } from '@/utilities/phone'
|
||||
import { normalizeBrazilianPhone } from '@/lib/phone'
|
||||
|
||||
export const positiveRelationshipId = z.number().int().positive()
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
positiveRelationshipId,
|
||||
trimmedNullableText,
|
||||
} from '@/lib/schemas/primitives'
|
||||
import { normalizeSearchPhrase } from '@/utilities/wordStartFilter'
|
||||
import { normalizeSearchPhrase } from '@/lib/wordStartFilter'
|
||||
|
||||
export const supporterVoteIntentions = ['certo', 'tende_a_certo', 'indeciso', 'outro'] as const
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { positiveRelationshipId, trimmedNullableText } from '@/lib/schemas/primi
|
|||
import {
|
||||
getVoteEstimateOrderViolation,
|
||||
VOTE_ESTIMATE_ORDER_ERROR_MESSAGE,
|
||||
} from '@/utilities/voteEstimate'
|
||||
} from '@/lib/voteEstimate'
|
||||
|
||||
const optionalEstimate = z.number().int().min(0).max(1_000_000).nullable().optional()
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
// Domain modules may import from here; this module must not import from them.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
import { isStaffCampaignRole, isUnrestrictedCampaignRole } from '@/lib/campaignRoles'
|
||||
import type { CampaignUser, User } from '@/payload-types'
|
||||
import type { Access, PayloadRequest, Where } from 'payload'
|
||||
|
||||
|
|
@ -47,15 +48,14 @@ export const isCampaignCandidate = (user: CampaignActor): boolean =>
|
|||
|
||||
/** Coordinator or candidate — unrestricted scope (all municipalities, decisions). */
|
||||
export const isCampaignUnrestricted = (user: CampaignActor): boolean =>
|
||||
isCampaignCoordinator(user) || isCampaignCandidate(user)
|
||||
isCampaignUser(user) && isUnrestrictedCampaignRole(user.role)
|
||||
|
||||
export const isCampaignLeader = (user: CampaignActor): boolean =>
|
||||
isCampaignUser(user) && user.role === 'leader'
|
||||
|
||||
/** Staff = coordinator, advisor, or candidate. Leaders are not staff. */
|
||||
export const isCampaignStaff = (user: CampaignActor): boolean =>
|
||||
isCampaignUser(user) &&
|
||||
(user.role === 'coordinator' || user.role === 'advisor' || user.role === 'candidate')
|
||||
isCampaignUser(user) && isStaffCampaignRole(user.role)
|
||||
|
||||
/**
|
||||
* Eligible relationship targets for advisor assignments (municipality / action plan).
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import 'server-only'
|
||||
|
||||
import type { Payload } from 'payload'
|
||||
|
||||
import type { ActionPlan, CampaignDemand, CampaignUser } from '@/payload-types'
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import 'server-only'
|
||||
|
||||
import type { Payload } from 'payload'
|
||||
|
||||
import type { ActionPlan, CampaignUser } from '@/payload-types'
|
||||
|
|
|
|||
|
|
@ -2,12 +2,18 @@ import 'server-only'
|
|||
|
||||
import type { Payload } from 'payload'
|
||||
|
||||
import {
|
||||
CAMPAIGN_INVITE_CONSENT_KEY,
|
||||
SUPPORTER_REGISTRATION_CONSENT_KEY,
|
||||
SUPPORTER_VOTE_INTENTION_CONSENT_KEY,
|
||||
} from '@/lib/campaignConsentKeys'
|
||||
import type { Consent } from '@/payload-types'
|
||||
import { CAMPAIGN_INVITE_CONSENT_KEY } from '@/utilities/campaignInvite'
|
||||
import { hashConsentContent } from '@/utilities/consentContentHash'
|
||||
|
||||
export const SUPPORTER_REGISTRATION_CONSENT_KEY = 'apoiador-cadastro'
|
||||
export const SUPPORTER_VOTE_INTENTION_CONSENT_KEY = 'apoiador-intencao-voto'
|
||||
export {
|
||||
SUPPORTER_REGISTRATION_CONSENT_KEY,
|
||||
SUPPORTER_VOTE_INTENTION_CONSENT_KEY,
|
||||
} from '@/lib/campaignConsentKeys'
|
||||
|
||||
export type ConsentDescriptor = {
|
||||
id: number
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import 'server-only'
|
||||
|
||||
import type { Payload } from 'payload'
|
||||
|
||||
import type { CampaignUser, VotePledge } from '@/payload-types'
|
||||
|
|
@ -5,8 +7,8 @@ import { loadMunicipalityScope } from '@/utilities/campaignMunicipalityScope'
|
|||
import type { MunicipalityGoalCoverage } from '@/utilities/goalCoverage'
|
||||
import { loadMunicipalityGoalCoverageBundle } from '@/utilities/municipalityGoalAccount'
|
||||
import { relationshipId, requireRelationshipId } from '@/utilities/relationship'
|
||||
import type { VoteEstimateScenario } from '@/utilities/voteEstimate'
|
||||
import { rollupMunicipalityStaffVotes } from '@/utilities/votePledgeData'
|
||||
import type { VoteEstimateScenario } from '@/lib/voteEstimate'
|
||||
import { rollupMunicipalityStaffVotes } from '@/utilities/votePledgeViews'
|
||||
|
||||
export type StaffDashboardView = {
|
||||
kind: 'staff'
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import 'server-only'
|
||||
|
||||
import type { Payload } from 'payload'
|
||||
|
||||
import type { CampaignDemandKind, CampaignDemandStatus } from '@/lib/schemas/campaignDemand'
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { createHash, randomBytes } from 'node:crypto'
|
||||
|
||||
import { buildWhatsAppUrl } from '@/utilities/phone'
|
||||
import { buildWhatsAppUrl } from '@/lib/phone'
|
||||
|
||||
export { CAMPAIGN_INVITE_CONSENT_KEY } from '@/lib/campaignConsentKeys'
|
||||
|
||||
export const CAMPAIGN_INVITE_CONSENT_KEY = 'lideranca-autopreenchimento'
|
||||
export const CAMPAIGN_INVITE_EXPIRATION_MS = 7 * 24 * 60 * 60 * 1000
|
||||
|
||||
export type CampaignInviteKind = 'login' | 'autopreenchimento'
|
||||
|
|
|
|||
|
|
@ -5,10 +5,8 @@ import { cache } from 'react'
|
|||
import type { Payload, Where } from 'payload'
|
||||
|
||||
import type { CampaignUser, Municipality } from '@/payload-types'
|
||||
import {
|
||||
aggregatePledgesByMunicipality,
|
||||
type MunicipalityPledgeAggregate,
|
||||
} from '@/utilities/votePledgeData'
|
||||
import { aggregatePledgesByMunicipality } from '@/utilities/votePledgeData'
|
||||
import { type MunicipalityPledgeAggregate } from '@/utilities/votePledgeViews'
|
||||
|
||||
/** Superset of the fields the dashboard, map, and list overview consume. */
|
||||
export type ScopedMunicipalityDoc = Pick<
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import 'server-only'
|
||||
|
||||
import type { Payload } from 'payload'
|
||||
|
||||
import type { RelationOption } from '@/components/campaign/RelationMultiSelect'
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import 'server-only'
|
||||
|
||||
import type { Config } from '@/payload-types'
|
||||
|
||||
import configPromise from '@payload-config'
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import 'server-only'
|
||||
|
||||
/**
|
||||
* Cache tag for every `unstable_cache` entry derived from the immutable TSE
|
||||
* election collections (2014/2018/2022). The data only changes when the seed
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import 'server-only'
|
||||
|
||||
import configPromise from '@payload-config'
|
||||
import { unstable_cache } from 'next/cache'
|
||||
import { getPayload } from 'payload'
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import 'server-only'
|
||||
|
||||
import { and, eq, type AnyColumn } from '@payloadcms/db-postgres/drizzle'
|
||||
import type { Payload } from 'payload'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import 'server-only'
|
||||
|
||||
import type { Config } from '@/payload-types'
|
||||
|
||||
import configPromise from '@payload-config'
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { formatElectionNumber } from '@/lib/electionInsights'
|
||||
import type { SuggestedGoalByScenario } from '@/utilities/municipalityPotential'
|
||||
import type { MunicipalityPledgeAggregate } from '@/utilities/votePledgeData'
|
||||
import type { MunicipalityPledgeAggregate } from '@/utilities/votePledgeViews'
|
||||
import {
|
||||
getVoteEstimateForScenario,
|
||||
VOTE_ESTIMATE_SCENARIOS,
|
||||
type VoteEstimateScenario,
|
||||
type VoteEstimateScenarioFields,
|
||||
} from '@/utilities/voteEstimate'
|
||||
} from '@/lib/voteEstimate'
|
||||
|
||||
/**
|
||||
* E8 "conta da cadeira" — coverage of the goal by auditable pledges.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import 'server-only'
|
||||
|
||||
import type { Payload } from 'payload'
|
||||
|
||||
import type { RelationOption } from '@/components/campaign/RelationMultiSelect'
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import 'server-only'
|
||||
|
||||
import type { Payload } from 'payload'
|
||||
|
||||
import type { SupportStatus } from '@/lib/schemas/leadership'
|
||||
|
|
|
|||
|
|
@ -5,11 +5,8 @@ import { cache } from 'react'
|
|||
|
||||
import { getMunicipalityFederalBaseline } from '@/lib/bahiaElectionAggregates'
|
||||
import type { Municipality } from '@/payload-types'
|
||||
import {
|
||||
aggregatePledgesByMunicipality,
|
||||
emptyMunicipalityPledgeAggregate,
|
||||
resolveMunicipalityStaffVoteTotal,
|
||||
} from '@/utilities/votePledgeData'
|
||||
import { aggregatePledgesByMunicipality } from '@/utilities/votePledgeData'
|
||||
import { emptyMunicipalityPledgeAggregate, resolveMunicipalityStaffVoteTotal } from '@/utilities/votePledgeViews'
|
||||
import {
|
||||
computeTerritoryRollup,
|
||||
type TerritoryMunicipalityInput,
|
||||
|
|
@ -18,7 +15,7 @@ import {
|
|||
import {
|
||||
DEFAULT_VOTE_ESTIMATE_SCENARIO,
|
||||
type VoteEstimateScenario,
|
||||
} from '@/utilities/voteEstimate'
|
||||
} from '@/lib/voteEstimate'
|
||||
|
||||
type MunicipalityDoc = Pick<
|
||||
Municipality,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import 'server-only'
|
||||
|
||||
import configPromise from '@payload-config'
|
||||
import { unstable_cache } from 'next/cache'
|
||||
import { getPayload, type Payload } from 'payload'
|
||||
|
|
|
|||
|
|
@ -31,11 +31,8 @@ import {
|
|||
type MunicipalityUpdateViewModel,
|
||||
} from '@/utilities/municipalityUpdatePageData'
|
||||
import type { MunicipalityDetailViewModel } from '@/utilities/municipalityViewModels'
|
||||
import {
|
||||
aggregateMunicipalityPledgesFromRows,
|
||||
loadMunicipalityPledges,
|
||||
type MunicipalityPledgeAggregate,
|
||||
} from '@/utilities/votePledgeData'
|
||||
import { loadMunicipalityPledges } from '@/utilities/votePledgeData'
|
||||
import { aggregateMunicipalityPledgesFromRows, type MunicipalityPledgeAggregate } from '@/utilities/votePledgeViews'
|
||||
|
||||
/**
|
||||
* E16 dossiê — section caps. The dossier is a 1–2 page pre-visit read, not a
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import 'server-only'
|
||||
|
||||
import configPromise from '@payload-config'
|
||||
import { unstable_cache } from 'next/cache'
|
||||
import { getPayload, type Payload } from 'payload'
|
||||
|
|
|
|||
|
|
@ -24,11 +24,8 @@ import {
|
|||
zeroByVoteEstimateScenario,
|
||||
type VoteEstimateScenarioFields,
|
||||
type VoteEstimateScenario,
|
||||
} from '@/utilities/voteEstimate'
|
||||
import {
|
||||
emptyMunicipalityPledgeAggregate,
|
||||
type MunicipalityPledgeAggregate,
|
||||
} from '@/utilities/votePledgeData'
|
||||
} from '@/lib/voteEstimate'
|
||||
import { emptyMunicipalityPledgeAggregate, type MunicipalityPledgeAggregate } from '@/utilities/votePledgeViews'
|
||||
|
||||
type GoalAccountReader = CampaignUser | User
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import {
|
|||
type RawSearchParams as CampaignListRawSearchParams,
|
||||
} from '@/utilities/campaignListUrl'
|
||||
import { politicalTrendLabels, type PoliticalTrendStatus } from '@/utilities/municipalityLabels'
|
||||
import { normalizeSearchPhrase } from '@/utilities/wordStartFilter'
|
||||
import { normalizeSearchPhrase } from '@/lib/wordStartFilter'
|
||||
|
||||
import type { Municipality } from '@/payload-types'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { HISTORICAL_SERIES_YEARS } from '@/lib/electionResults'
|
||||
import type { MunicipalitiesByIbgeCode } from '@/utilities/municipalityMapNavigation'
|
||||
import type { VoteEstimateScenario } from '@/utilities/voteEstimate'
|
||||
import type { VoteEstimateScenario } from '@/lib/voteEstimate'
|
||||
|
||||
/**
|
||||
* Client-safe contract of the municipality map: the bundle shape the server
|
||||
|
|
|
|||
|
|
@ -30,12 +30,8 @@ import {
|
|||
DEFAULT_VOTE_ESTIMATE_SCENARIO,
|
||||
VOTE_ESTIMATE_SCENARIOS,
|
||||
type VoteEstimateScenario,
|
||||
} from '@/utilities/voteEstimate'
|
||||
import {
|
||||
emptyMunicipalityPledgeAggregate,
|
||||
resolveMunicipalityStaffVoteTotal,
|
||||
type MunicipalityPledgeAggregate,
|
||||
} from '@/utilities/votePledgeData'
|
||||
} from '@/lib/voteEstimate'
|
||||
import { emptyMunicipalityPledgeAggregate, resolveMunicipalityStaffVoteTotal, type MunicipalityPledgeAggregate } from '@/utilities/votePledgeViews'
|
||||
|
||||
export type { MunicipalityMapSlugEntry, MunicipalitiesByIbgeCode } from '@/utilities/municipalityMapNavigation'
|
||||
// Client components import the map vocabulary from the contract module; the
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import 'server-only'
|
||||
|
||||
import type { Payload, Where } from 'payload'
|
||||
|
||||
import { bahiaIdentityTerritories } from '@/lib/bahiaTerritories'
|
||||
|
|
@ -33,11 +35,8 @@ import {
|
|||
} from '@/utilities/municipalityViewModels'
|
||||
import { relationshipId } from '@/utilities/relationship'
|
||||
import { loadStateDeputySummaries } from '@/utilities/stateDeputyData'
|
||||
import { type VoteEstimateScenario } from '@/utilities/voteEstimate'
|
||||
import {
|
||||
rollupMunicipalityStaffVotes,
|
||||
type MunicipalityPledgeAggregate,
|
||||
} from '@/utilities/votePledgeData'
|
||||
import { type VoteEstimateScenario } from '@/lib/voteEstimate'
|
||||
import { rollupMunicipalityStaffVotes, type MunicipalityPledgeAggregate } from '@/utilities/votePledgeViews'
|
||||
|
||||
export class MunicipalityNotFoundError extends Error {
|
||||
override name = 'MunicipalityNotFoundError'
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {
|
|||
import { municipalityCatalog } from '@/lib/municipalityCatalog'
|
||||
import type { BahiaIdentityTerritory } from '@/lib/bahiaTerritories'
|
||||
import type { CampaignGoal } from '@/payload-types'
|
||||
import type { VoteEstimateScenario } from '@/utilities/voteEstimate'
|
||||
import type { VoteEstimateScenario } from '@/lib/voteEstimate'
|
||||
|
||||
/**
|
||||
* E8 "conta da cadeira" — derived electoral potential per municipality, built
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import 'server-only'
|
||||
|
||||
import type { Payload } from 'payload'
|
||||
|
||||
import type {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import 'server-only'
|
||||
|
||||
import type { Payload } from 'payload'
|
||||
|
||||
import type { MunicipalityVoteRankEntry } from '@/lib/municipalityVoteRank'
|
||||
|
|
@ -12,9 +14,9 @@ import {
|
|||
toVoteEstimateScenarioViewModel,
|
||||
type VoteEstimateScenario,
|
||||
type VoteEstimateScenarioViewModel,
|
||||
} from '@/utilities/voteEstimate'
|
||||
import type { MunicipalityPledgeAggregate } from '@/utilities/votePledgeData'
|
||||
import { createEmptyMunicipalityPledgeAggregate } from '@/utilities/votePledgeData'
|
||||
} from '@/lib/voteEstimate'
|
||||
import type { MunicipalityPledgeAggregate } from '@/utilities/votePledgeViews'
|
||||
import { createEmptyMunicipalityPledgeAggregate } from '@/utilities/votePledgeViews'
|
||||
|
||||
export const municipalityListSelect = {
|
||||
name: true,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import 'server-only'
|
||||
|
||||
import type { Payload } from 'payload'
|
||||
|
||||
import type { OrganizationKind } from '@/lib/schemas/organization'
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import 'server-only'
|
||||
|
||||
import type { Post, Tag } from '@/payload-types'
|
||||
|
||||
import configPromise from '@payload-config'
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import 'server-only'
|
||||
|
||||
import type { Payload } from 'payload'
|
||||
|
||||
import type { CampaignUser, Leadership, StateDeputy } from '@/payload-types'
|
||||
|
|
|
|||
|
|
@ -1,8 +1,20 @@
|
|||
import type { Where } from 'payload'
|
||||
|
||||
import { isContactSearchQueryReady, normalizeContactSearchQuery } from '@/lib/contactSearchQuery'
|
||||
import { normalizeBrazilianPhone } from '@/utilities/phone'
|
||||
import type { SupporterListState } from '@/utilities/supporterUi'
|
||||
import { normalizeBrazilianPhone } from '@/lib/phone'
|
||||
import type { SupporterVoteIntention } from '@/lib/schemas/supporter'
|
||||
|
||||
/**
|
||||
* The filters the where-builder actually reads — a structural subset of
|
||||
* `SupporterListState` (`supporterUi.ts`), declared here so the type flows
|
||||
* url-module → where-builder without a module cycle.
|
||||
*/
|
||||
export type SupporterListFilterInput = {
|
||||
q?: string
|
||||
voteIntention?: SupporterVoteIntention
|
||||
city?: string
|
||||
municipality?: number
|
||||
}
|
||||
|
||||
export type SupporterSearchTerms = {
|
||||
q: string
|
||||
|
|
@ -35,7 +47,7 @@ const buildPayloadSearchWhere = (terms: SupporterSearchTerms): Where => {
|
|||
return { or: searchFilters }
|
||||
}
|
||||
|
||||
export const toPayloadWhere = (state: SupporterListState): Where => {
|
||||
export const toPayloadWhere = (state: SupporterListFilterInput): Where => {
|
||||
const filters: Where[] = []
|
||||
|
||||
if (state.q) {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import {
|
|||
buildSupporterSearchTerms,
|
||||
type SupporterSearchTerms,
|
||||
} from '@/utilities/supporterListFilters'
|
||||
import type { SupporterListState } from '@/utilities/supporterUi'
|
||||
import type { SupporterListFilterInput } from '@/utilities/supporterListFilters'
|
||||
|
||||
export type AggregateSqlConditions = {
|
||||
conditions: ReturnType<typeof sql>[]
|
||||
|
|
@ -28,7 +28,7 @@ const buildSqlSearchCondition = (terms: SupporterSearchTerms): ReturnType<typeof
|
|||
return sql`(${sql.join(searchTerms, sql` OR `)})`
|
||||
}
|
||||
|
||||
export const toAggregateSqlConditions = (state: SupporterListState): AggregateSqlConditions => {
|
||||
export const toAggregateSqlConditions = (state: SupporterListFilterInput): AggregateSqlConditions => {
|
||||
const conditions: ReturnType<typeof sql>[] = []
|
||||
let needsContactJoin = false
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import 'server-only'
|
||||
|
||||
import type { Payload } from 'payload'
|
||||
|
||||
import type { RelationOption } from '@/components/campaign/RelationMultiSelect'
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
resolveBahiaMunicipality,
|
||||
type SupporterVoteIntention,
|
||||
} from '@/lib/schemas/supporter'
|
||||
import { isStaffCampaignRole } from '@/lib/campaignRoles'
|
||||
import type { CampaignUser, Supporter } from '@/payload-types'
|
||||
import {
|
||||
buildListHref,
|
||||
|
|
@ -122,4 +123,4 @@ export const getSupporterScopeLabel = (total: number): string =>
|
|||
|
||||
/** Staff roles with supporter-area access — matches collection-level supporter access. */
|
||||
export const canAccessSupporterArea = (role: CampaignUser['role']): boolean =>
|
||||
role === 'coordinator' || role === 'advisor' || role === 'candidate'
|
||||
isStaffCampaignRole(role)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { territoryForCity } from '@/lib/bahiaTerritories'
|
||||
import type { SupporterVoteIntention } from '@/lib/schemas/supporter'
|
||||
import type { CampaignUser, Contact, Municipality, Supporter } from '@/payload-types'
|
||||
import { formatBrazilianPhoneInput } from '@/utilities/phone'
|
||||
import { formatBrazilianPhoneInput } from '@/lib/phone'
|
||||
import { isPopulatedRelationship } from '@/utilities/relationship'
|
||||
import { supporterSourceLabels } from '@/utilities/supporterUi'
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue