mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-15 10:33:11 +00:00
220 lines
7.2 KiB
TypeScript
220 lines
7.2 KiB
TypeScript
/** Client-safe entry paths for UX-1 action wizards under `/campanha/acoes`. */
|
|
|
|
import { CAMPAIGN_HOME } from '@/lib/campaignPaths'
|
|
import {
|
|
parsePoliticalTrendStatusFormValue,
|
|
type PoliticalTrendStatusValue,
|
|
} from '@/lib/schemas/municipality'
|
|
|
|
export const CAMPAIGN_ACTIONS_HOME = '/campanha/acoes' as const
|
|
|
|
export type CampaignWizardActionId =
|
|
| 'update-votes'
|
|
| 'register-update'
|
|
| 'change-trend'
|
|
| 'update-leadership'
|
|
| 'register-demand'
|
|
|
|
export const CAMPAIGN_WIZARD_ACTION_SLUGS: Record<CampaignWizardActionId, string> = {
|
|
'update-votes': 'atualizar-votos',
|
|
'register-update': 'registrar-atualizacao',
|
|
'change-trend': 'mudar-tendencia',
|
|
'update-leadership': 'atualizar-lideranca',
|
|
'register-demand': 'registrar-pedido',
|
|
}
|
|
|
|
const wizardActionSlugs = new Set(Object.values(CAMPAIGN_WIZARD_ACTION_SLUGS))
|
|
|
|
const campaignWizardSlugToId = Object.fromEntries(
|
|
Object.entries(CAMPAIGN_WIZARD_ACTION_SLUGS).map(([id, actionSlug]) => [
|
|
actionSlug,
|
|
id as CampaignWizardActionId,
|
|
]),
|
|
) as Record<string, CampaignWizardActionId>
|
|
|
|
export const isCampaignWizardActionSlug = (slug: string): boolean => wizardActionSlugs.has(slug)
|
|
|
|
export const campaignWizardActionIdForSlug = (slug: string): CampaignWizardActionId | undefined =>
|
|
campaignWizardSlugToId[slug]
|
|
|
|
export const isCampaignWizardActionId = (id: string): id is CampaignWizardActionId =>
|
|
id in CAMPAIGN_WIZARD_ACTION_SLUGS
|
|
|
|
export const campaignActionEntryHref = (id: CampaignWizardActionId): string =>
|
|
`${CAMPAIGN_ACTIONS_HOME}/${CAMPAIGN_WIZARD_ACTION_SLUGS[id]}`
|
|
|
|
export const WIZARD_MUNICIPIO_QUERY_KEY = 'municipio' as const
|
|
|
|
/** Legacy B61 ritual param — canonical URLs omit it (B77). */
|
|
export const WIZARD_SCENARIO_QUERY_KEY = 'cenario' as const
|
|
|
|
export const WIZARD_TREND_STATUS_QUERY_KEY = 'trendStatus' as const
|
|
|
|
export const WIZARD_LEADERSHIP_ID_QUERY_KEY = 'leadershipId' as const
|
|
|
|
export const WIZARD_RETURN_PATH_QUERY_KEY = 'from' as const
|
|
|
|
const WIZARD_RETURN_PATH_AUTH_PREFIXES = [
|
|
'/campanha/login',
|
|
'/campanha/convite',
|
|
'/campanha/redefinir-senha',
|
|
'/campanha/webauthn',
|
|
] as const
|
|
|
|
/** Allowlisted internal return paths for wizard dismiss / return (B110). */
|
|
export const isWizardReturnPath = (pathname: string): boolean => {
|
|
if (!pathname.startsWith('/campanha')) return false
|
|
if (pathname.startsWith('/campanha/acoes')) return false
|
|
for (const prefix of WIZARD_RETURN_PATH_AUTH_PREFIXES) {
|
|
if (pathname === prefix || pathname.startsWith(`${prefix}/`)) return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
export const parseWizardReturnPath = (value: string | string[] | undefined): string | undefined => {
|
|
const raw = Array.isArray(value) ? value[0] : value
|
|
const trimmed = raw?.trim()
|
|
if (!trimmed) return undefined
|
|
if (trimmed.includes('://') || trimmed.startsWith('//')) return undefined
|
|
const path = trimmed.startsWith('/') ? trimmed : `/${trimmed}`
|
|
if (path.includes('?') || path.includes('#')) return undefined
|
|
return isWizardReturnPath(path) ? path : undefined
|
|
}
|
|
|
|
export const appendWizardReturnPath = (href: string, returnPath?: string): string => {
|
|
if (!returnPath || !isWizardReturnPath(returnPath)) return href
|
|
const queryIndex = href.indexOf('?')
|
|
const base = queryIndex === -1 ? href : href.slice(0, queryIndex)
|
|
const params = new URLSearchParams(queryIndex === -1 ? '' : href.slice(queryIndex + 1))
|
|
params.set(WIZARD_RETURN_PATH_QUERY_KEY, returnPath)
|
|
return `${base}?${params.toString()}`
|
|
}
|
|
|
|
export type WizardActionHrefOptions = {
|
|
leadershipId?: number
|
|
returnPath?: string
|
|
}
|
|
|
|
export const wizardActionHref = (
|
|
actionSlug: string,
|
|
municipalitySlug?: string,
|
|
options?: WizardActionHrefOptions,
|
|
): string => {
|
|
const base = `${CAMPAIGN_ACTIONS_HOME}/${actionSlug}`
|
|
if (!municipalitySlug && options?.leadershipId === undefined) {
|
|
return appendWizardReturnPath(base, options?.returnPath)
|
|
}
|
|
const params = new URLSearchParams()
|
|
if (municipalitySlug) {
|
|
params.set(WIZARD_MUNICIPIO_QUERY_KEY, municipalitySlug)
|
|
}
|
|
if (options?.leadershipId !== undefined) {
|
|
params.set(WIZARD_LEADERSHIP_ID_QUERY_KEY, String(options.leadershipId))
|
|
}
|
|
const href = params.size > 0 ? `${base}?${params.toString()}` : base
|
|
return appendWizardReturnPath(href, options?.returnPath)
|
|
}
|
|
|
|
export const parseWizardMunicipioParam = (
|
|
value: string | string[] | undefined,
|
|
): string | undefined => {
|
|
const raw = Array.isArray(value) ? value[0] : value
|
|
const trimmed = raw?.trim()
|
|
return trimmed ? trimmed : undefined
|
|
}
|
|
|
|
export const parseWizardLeadershipIdParam = (
|
|
value: string | string[] | undefined,
|
|
): number | undefined => {
|
|
const raw = Array.isArray(value) ? value[0] : value
|
|
const trimmed = raw?.trim()
|
|
if (!trimmed || !/^[1-9]\d*$/.test(trimmed)) {
|
|
return undefined
|
|
}
|
|
return Number(trimmed)
|
|
}
|
|
|
|
export const hasWizardScenarioParam = (value: string | string[] | undefined): boolean =>
|
|
parseWizardMunicipioParam(value) !== undefined
|
|
|
|
export const resolveWizardTrendStatusParam = (
|
|
value: string | string[] | undefined,
|
|
): { trendStatus?: PoliticalTrendStatusValue; invalid: boolean } => {
|
|
const raw = Array.isArray(value) ? value[0] : value
|
|
const trimmed = raw?.trim()
|
|
if (!trimmed) {
|
|
return { trendStatus: undefined, invalid: false }
|
|
}
|
|
|
|
const trendStatus = parsePoliticalTrendStatusFormValue(trimmed)
|
|
if (trendStatus) {
|
|
return { trendStatus, invalid: false }
|
|
}
|
|
|
|
return { trendStatus: undefined, invalid: true }
|
|
}
|
|
|
|
export const wizardTrendHref = (
|
|
actionSlug: string,
|
|
municipalitySlug?: string,
|
|
trendStatus?: PoliticalTrendStatusValue,
|
|
returnPath?: string,
|
|
): string => {
|
|
const base = `${CAMPAIGN_ACTIONS_HOME}/${actionSlug}`
|
|
if (!municipalitySlug) {
|
|
return appendWizardReturnPath(base, returnPath)
|
|
}
|
|
|
|
const params = new URLSearchParams({ [WIZARD_MUNICIPIO_QUERY_KEY]: municipalitySlug })
|
|
if (trendStatus) {
|
|
params.set(WIZARD_TREND_STATUS_QUERY_KEY, trendStatus)
|
|
}
|
|
return appendWizardReturnPath(`${base}?${params.toString()}`, returnPath)
|
|
}
|
|
|
|
/** Dismiss / return target for wizard steps: allowlisted `from`, else Início (B110). */
|
|
export const wizardReturnHref = (returnPath?: string): string =>
|
|
returnPath && isWizardReturnPath(returnPath) ? returnPath : CAMPAIGN_HOME
|
|
|
|
/**
|
|
* Principal wizard steps (register-update, update-votes, change-trend,
|
|
* update-leadership) share the municipality-search route for "back".
|
|
*/
|
|
type WizardStepKind =
|
|
| 'municipality-search'
|
|
| 'votes'
|
|
| 'trend-choice'
|
|
| 'trend-note'
|
|
| 'update-body'
|
|
| 'leadership-grid'
|
|
| 'leadership-form'
|
|
| 'register-demand'
|
|
|
|
type WizardPreviousHrefInput = {
|
|
actionSlug: string
|
|
stepKind: WizardStepKind
|
|
municipalitySlug?: string
|
|
returnPath?: string
|
|
}
|
|
|
|
/**
|
|
* Href for the logically previous wizard step — internal sub-steps only. Every
|
|
* principal wizard step goes back to the municipality search of its own flow.
|
|
*/
|
|
export const wizardPreviousHref = (input: WizardPreviousHrefInput): string => {
|
|
const { actionSlug, stepKind, municipalitySlug, returnPath } = input
|
|
|
|
if (stepKind === 'municipality-search') {
|
|
return wizardReturnHref(returnPath)
|
|
}
|
|
|
|
if (stepKind === 'trend-note') {
|
|
return wizardTrendHref(actionSlug, municipalitySlug, undefined, returnPath)
|
|
}
|
|
|
|
if (stepKind === 'leadership-form') {
|
|
return wizardActionHref(actionSlug, municipalitySlug, { returnPath })
|
|
}
|
|
|
|
return wizardActionHref(actionSlug, undefined, { returnPath })
|
|
}
|