Components Directory
Location: src/components/
Purpose: React components organized by feature domain and type.
Top-Level Layout
components/
├── auth/ # Sign-in / sign-up surface widgets
├── campaign/ # Campaign create/update + list view
├── common/ # Shared layout pieces (sidebar, navbar, no-access, switchers, form fields)
├── dashboard/ # Configurable per-campaign dashboards + chart primitives
├── forms/ # Form bodies + Zod schemas (one folder per domain)
├── permission-sets/ # "Responsibilities" UI (permission set + matrix table)
├── permissions/ # Permission keys list/CRUD wrappers
├── petitions/ # Petition + multi-step signature entry workflow
├── rates/ # Rate forms, server actions, rates table
├── regions/ # Region forms, server actions, regions table
├── roles/ # Role create/update + permission manager
├── routes/ # Sidebar route components (one per nav section)
├── signatures/ # Signatures table + voter cards
├── superAdmin/ # Super-admin-only widgets (campaign logo, credentials CRUD)
├── tables/ # TanStack Table contexts (one folder per domain) + shared utilities
├── teams/ # Team CRUD, sub-teams, invites, responsibility-matrix, member admin
├── transactions/ # Transaction forms, server actions, table
├── turn-in/ # Turn-in record forms, actions, table
├── ui/ # Reusable Radix UI primitives (shadcn/ui)
└── voter-search/ # Voter search forms + ES result cards
Highlights & Conventions
common/app-sidebar.tsxis the master sidebar. It acceptslayout: "root" | "teams" | "campaign" | "circulator"and composes the per-section route components below.routes/holds one component per sidebar section. Each one readspermissionKeysfromTeamsContextand conditionally renders nav items:campaign-routes.tsx— Campaign nav (My Dashboard, Petitions, Validators, Circulators, Signatures, Households).campaign-admin-routes.tsx— Manage Campaign, Admin Dashboard, Manage Petitions, Sub Teams, Regions.accounting-routes.tsx— Turn-Ins, Rates, Transactions (URL flips between team-scoped and campaign-scoped based onparams.slug).circulator-routes.tsx— Dashboard by Campaign / by Turn-in Date.common-routes.tsx— Voter Search, Admin Voter Search.super-admin-routes.tsx— Credentials, All Teams, Roles, Responsibilities, Permissions.team-management-routes.tsx— Team Dashboard, Campaigns, Members.user-settings-route.tsx— User account settings.
tables/common/holds the shared table machinery used by every per-domain table:useFetchTableData.tsx— TanStack-friendly fetcher with caching keyed bygenerateCacheKey.parseTableStateFromURL.ts— Server-side table-state parsing.filters.ts,utils.ts,tables.ts— query-builder helpers.data-table.tsx,data-table-pagination.tsx,dropDownFilter.tsx,advanceFilter.tsx,date-field-filter.tsx,activeFilterBadges.tsx,ColumnVisibilityDropdown.tsx,TableSkeleton.tsx,tableStatCard.tsx.
forms/contains the form bodies + Zod schemas (e.g.petitionForm/petition.schema.ts,dashboards/dashboards.schema.ts). Each form is paired with a corresponding server action undersrc/actions/**.forms/dashboards/dataSources/holds the metadata describing chartable Supabase tables (e.g.signaturesMetadata.ts).teams/responsibility-matrix/is the editable matrix for assigning permission sets across users × campaigns (form + table + dialogs + zod schema + utility helpers).signatures/voter-cards/+petitions/Signatures/make up the multi-step signature workflow (step-1-county→step-5-signatures-review) including circulator search, badges, and rejection flow.common/no-access.tsx+common/refresh-permissions-button.tsxform the access-denied screen reachable fromrequireAccessredirects.
Per-Folder Notes
common/
├── app-sidebar.tsx # Composed sidebar (uses every routes/* component)
├── nav-main.tsx, nav-projects.tsx, nav-user.tsx
├── navbar.tsx, page-title.tsx, profileMenu.tsx
├── no-access.tsx # Access denied UI
├── refresh-permissions-button.tsx # POSTs to /api/permissions/revalidate
├── access-not-found.tsx # Generic guard fallback
├── campaign-no-acess.tsx # Campaign-specific guard fallback
├── campaign-not-found.tsx, format-not-found-solo.tsx, formats-not-found.tsx
├── general-record-not-found.tsx, petition-not-found.tsx
├── campaign-switcher.tsx, team-switcher.tsx
├── region-picker.tsx, tag-filter-dropdown.tsx
├── statsCard.tsx, statsCardDivider.tsx, circulatorDashboard.tsx
├── reactive-user.tsx, remove-user.tsx
├── local-time.tsx, theme-switcher.tsx, LinkButton.tsx, loadingButton.tsx
├── fieldsGroup.tsx, body-container.tsx
├── formComponents/ # asyncSelectForm, asyncMultiSelectForm, checkboxForm, choiceButtonsForm,
│ # colorPickerForm, credentialsAsyncForm, dateSelectForm, dateTimePickerForm,
│ # formatListForm, ImageUploadForm, InputComponentForm, InputTagsForm,
│ # multiSelectForm, passwordForm, phone-input-form(-2), radioGroupForm,
│ # singleSelectForm, switchForm, TextAreaComponentForm, zipForm
└── loadingAnimation/ # CSS + component for global loading
dashboard/
├── dashboards-create.tsx, dashboards-update.tsx
├── dashboardsPageHeader.tsx, dashIDPageHeader.tsx
├── refresh-charts-button.tsx
├── campaign-stats/campaignStatsCard.tsx
└── charts/{areaChart,chartBarMultiple,lineChart,pieChart,radarChart,radialChart}.tsx
forms/
├── authForms/{loginForm,signUpForm,profileUpdateForm}.tsx + authForms.schema.ts
├── campaignForm/{campaignForm.tsx, campaign.schema.ts}
├── credentialsForm/{credentialsForm.tsx, credentials.schema.ts}
├── dashboards/ # dashboardsForm.tsx, chartFieldsConfig, chartFiltersConfig, chartData
│ └── dataSources/ # chart-source registry (e.g. signaturesMetadata.ts)
├── permissionsKeys/ # PermissionKeyRow + add/edit forms + schema
├── petitionForm/{petitionForm.tsx, petition.schema.ts}
└── roles/{roleForm.tsx, role.schema.ts}
permission-sets/
├── permission-keys-form-{table,toolbar,data-table}.tsx
├── forms/ # permission-sets-create.tsx, permission-sets-update.tsx,
│ # permission-setsForm.tsx, permission-sets.schema.ts
└── table-view/ # PermissionSetsTable + columns/context
petitions/
├── changeFormatDropdown.tsx, PetitionSignaturesData.tsx
├── petition-create.tsx, petition-update.tsx
├── SelectPetitionFormat.tsx, SelectPetitionFormatDescription.tsx
├── voterSearchInput.tsx
├── signatureReview/{groupCard,reviewCard}.tsx
└── Signatures/ # Multi-step signature workflow + cards/badges
rates/
├── actions/{create-rate-action,update-rate-action}.ts
├── forms/{rateForm.tsx, rate-create.tsx, rate-update.tsx, rate.schema.ts}
└── rates-table/{RatesTable.tsx, useRatesContext.tsx, useGetRatesTableColumns.tsx, ratesColumnKeys.ts}
regions/
├── actions/{add-region-action,update-regions-action}.ts
├── forms/{regionsForm.tsx, regionsForm-create.tsx, regionsForm-update.tsx, regions.schema.ts}
└── dashboard/{RegionsTable.tsx, RegionsTableColumnKeys.ts, useRegionsTableContext.tsx, useGetRegionsTableColumns.tsx}
tables/
├── circulators-view/, credentials/, households-view/, permission-keys/,
├── petitions/, roles/, sub-teams/, teams-users/, users/, validators-view/
└── common/ # Shared TanStack Table helpers (see Highlights above)
teams/
├── add-{admin,member,sub-team}.tsx, add-member-dialog.tsx
├── choseTeam.tsx, member-{active-campaign,campaigns-update,role-update}.tsx
├── reload-admin-email.tsx, team-{create,update}.tsx, team-members-card.tsx
├── update-sub-team.tsx
├── accept-invites/{accept-invite,accept-campaign-invite}.tsx
├── actions/ # check-user-by-mail, member-camapign-update-action,
│ # member-role-update-action, new-member-action
├── forms/ # campaign-permissions, dashboardSelect, memberRoleForm,
│ # subTeamForm, teamForm, teamMemberCampaignForm, teamMemberForm,
│ # team.schema.ts, user-campaign
├── responsibility-matrix/ # Editable matrix (form, table, save dialog, schema, utils, context)
└── table/ # TeamsTable + columns/context
transactions/
├── actions/{create,update}-transaction-action.ts
├── forms/{transactionForm.tsx, transaction-{create,update}.tsx, transaction.schema.ts}
└── transactions-table/
turn-in/
├── actions/{add,update}-turnIn-action.ts, turnIn-records-admin-action.ts
├── dashboard/{TurnInTable.tsx, useTurnInTableContext.tsx, useGetTurnInTableColumns.tsx, TurnInTableColumnKeys.ts}
└── forms/{turnInForm.tsx, turnInForm-create.tsx, turnInForm-update.tsx,
turnInForm-admin-update.tsx, turnInAdminForm.tsx, turnIn.schema.ts}
ui/ # shadcn/ui + custom primitives (see your installed shadcn config)
├── accordion, async-multi-select, async-select, avatar, badge, breadcrumb, button, calendar, card,
├── chart, checkbox, citySearch, collapsible, color-picker, command, CurrentStepTitle, dashboard,
├── datePicker, dateTimePicker, dialog, dropdown-menu, errorWithoutContext, floatingInput, form,
├── hover-card, input(-otp/-group), inputTags, label, multiple-selector, password-input, placeSearch,
├── popover(-with-arrow), radio-group, scroll-area, select, separator, sheet, sidebar, singleSelect{Field,Search},
├── skeleton, spinner, stepper, switch(-embed-label), table, tabs, textarea, toast, toaster,
├── toggle(-group), tooltip
├── fileUpload/ # Dropzone, prompt UI, selected image, PDF preview
└── phone-input/ # countries.ts + phoneInput + use-state-history