Technical Reference
Companies

Companies — Technical Reference

This page covers the data models, architecture, and security rules behind the B2B Companies module.

Data Models

companies

Stores organization-level records.

ColumnTypeDescription
iduuidPrimary key
workspace_iduuidTenant isolation
nametextRequired. The organization's name
domaintextOptional website domain (e.g., acme.com)
phonetextOptional main phone number
address_line1textOptional street address line 1
address_line2textOptional street address line 2
citytextOptional city
statetextOptional state or province
postal_codetextOptional ZIP / postal code
countrytextOptional country
notestextOptional free-form notes
created_byuuidFK → user_profiles.id (creator)
updated_byuuidFK → user_profiles.id (last updater)
created_attimestamptzRecord creation timestamp
updated_attimestamptzRecord update timestamp

company_contacts

Junction table mapping contacts to companies in a many-to-many relationship.

ColumnTypeDescription
company_iduuidPrimary Key, FK → companies.id (ON DELETE CASCADE)
contact_iduuidPrimary Key, FK → contacts.id (ON DELETE CASCADE)
job_titletextContact's role at the company
work_emailtextBusiness email
work_phonetextBusiness phone
is_primarybooleanFlag designating the primary contact for the organization

Design Note: A unique constraint on (company_id, contact_id) prevents duplicate associations. Both foreign keys use ON DELETE CASCADE to automatically clean up relationship mappings when either a company or a contact is deleted.


Architecture

Module Gating & UI Tabs

The Companies module is an optional feature. Access is controlled via workspace-level feature toggles:

  • The /companies sidebar link is filtered by the workspace module toggle status.
  • The Companies tab on the Contact detail view is conditionally rendered based on the companies module toggle.
  • The Deals tab on the Company detail view is conditionally rendered based on both the companies and deals module toggles.

Case-Insensitive Search & Debounce

The /companies directory table features a search bar that queries the backend:

  • Filters by name, domain, or phone using a case-insensitive partial match (ILIKE).
  • Debounced by 300ms on the client side to minimize redundant API and database server requests during user input.

Live Contact Selection Picker

When linking a contact to a company:

  • The dropdown list triggers a database search only after a minimum of 2 characters is typed.
  • Already linked contact IDs are passed to the query to filter them out of the search results, preventing duplicate associations.

Phone Number Formatting

Main organization phone numbers and coworker work phone numbers are normalized for display as (XXX) XXX-XXXX using the same cosmetic formatting utility applied to contact phone numbers. Raw database values remain unformatted.

Company Detail Query (getCompanyById)

To populate the Company Detail view, the getCompanyById server action retrieves the company record and performs database joins to resolve:

  • created_by_profile: Resolves the user profile name (first_name, last_name) of the team member who created the company.
  • updated_by_profile: Resolves the user profile name (first_name, last_name) of the team member who last modified the company.

These profiles are used to render the standard unified vertical audit trails in the UI.


Performance & Hydration Optimization

Server-Side Pre-fetching & Lazy Loading

  • Server Component (pages/companies/page.tsx): Pre-fetches the initial getCompanies list server-side.
  • Client Component (companies-client.tsx): Manages search, client filters, and list sorting.
  • Dynamic imports: The AddCompanyDialog is lazily and conditionally imported on demand, reducing the bundle size.
  • State gates: Uses isFirstQueryRef to prevent duplicate fetches on initial load and initialWorkspaceIdRef sync hooks to reset filters only on workspace changes.

Security

Row-Level Security (RLS)

The companies and company_contacts tables enforce workspace-level isolation using RLS. Deletion operations are additionally restricted in code to users with admin or owner roles.

TableOperationAllowed Roles / Policies
companiesSELECTAll workspace members
companiesINSERTAll workspace members
companiesUPDATEAll workspace members
companiesDELETEWorkspace members with Admin or Owner roles (requireRole)
company_contactsSELECTAll workspace members
company_contactsINSERTAll workspace members
company_contactsUPDATEAll workspace members
company_contactsDELETEAll workspace members

Note: Although any member can link or unlink a contact (modifying company_contacts), only users with role Admin or Owner can delete a companies record entirely.