← Work
Field Services·Business Application · Flagship

Texas Hail & Dent Co.

Two-layer delivery: a storm-coded public platform built for automotive PDR conversion, plus a custom role-based invoice management system handling the full field operations lifecycle.

Live site ↗txhaildent.com

Public platform — storm-coded design language, urgency-forward layout, direct conversion path for vehicle owners evaluating automotive PDR after a hail event.

2
Delivery Layers
Public marketing platform + custom RBAC business application — one engagement, two distinct systems
4
Access Roles
Owner, Technician, Viewer, Admin — distinct interfaces with database-level enforcement
6
Invoice States
Draft → Submitted → Reviewed → Finalized → Signed → Sent — one-way, trigger-enforced
2
DB Triggers
State machine progression + content lock after finalization — enforced at the database, not application code
Engagement Overview

One engagement, two distinct systems

Layer 1

Public Platform

Storm-coded marketing site built for vehicle owners and fleet operators evaluating automotive PDR. Dark, authoritative design with video hero, before/after proof, trust signals, and a direct conversion path.

Layer 2

Business Application

Custom RBAC invoice management system with four distinct roles, a trigger-enforced lifecycle state machine, content lock after finalization, and mobile-first field tooling for technicians on job sites.

The Challenge

A field service business that outgrew standard platforms

Paintless dent repair businesses operate across multiple contexts simultaneously: field technicians creating estimates on job sites, owners needing full operational visibility, and external stakeholders requiring read-only access. No off-the-shelf platform handled this combination without significant compromise.

The engagement required both a conversion-first public presence — built for the urgency window immediately after a storm event — and a role-aware internal application with clear, enforceable boundaries between what each role can see and do.

Layer 1 — Public Platform

Built for the storm window

The public site is designed for a specific conversion context: vehicle owners evaluating automotive PDR options immediately after a hail event. The design language is intentional — dark, authoritative, storm-coded — with visual proof of work at the center of every decision path.

Before and after PDR transformation — proof-first conversion element

Before/after transformation slider — proof of craft at the center of the conversion path, not buried in a gallery.

  • ✓Hail event video hero — establishes urgency and authority on first load
  • ✓Before/after transformation slider — visual proof drives conversion more than copy
  • ✓Trust signal grid — certifications, service scope, operator credibility
  • ✓Structured FAQ — addresses objections before the customer asks
  • ✓Frictionless CTA funnel — single path from awareness to contact
Layer 2 — Business Application

Role-based access control

The invoice system supports four roles with distinct interfaces and database-level access enforcement via Supabase Row Level Security. Roles are resolved through a security-definer SQL function — preventing recursion in the RLS policies themselves.

RoleAccess Scope
OwnerFull access — all invoices, user management, settings
TechnicianCreate and edit own invoices only — field-scoped visibility
ViewerRead-only across all invoices — for external stakeholders
AdminSQL-level access only — never assignable through the UI

RLS policies enforce access at the PostgREST layer — application code cannot override them regardless of what is sent to the API.

Invoice Lifecycle

One-way state machine, enforced by the database

Invoice status progresses through a one-way state machine enforced by a database trigger. No role can reverse a status transition. A second trigger locks content columns after Finalized is reached — preventing retroactive alteration of billed records.

Draft

Created by technician in the field

→

Submitted

Sent for owner review

→

Reviewed

Owner has reviewed — ready to finalize

→

Finalized

Fields lock — content becomes immutable

→

Signed

Client signature recorded

→

Sent

Invoice delivered to client via email

Invoice management view — owner role with full lifecycle controls
Invoice View
Owner role — full invoice detail with lifecycle status and controls. Field data captured by technician, reviewed and finalized by ownership.
Client signature capture — collected on the Signed state transition
Signature Capture
Client signature recorded on the Signed state transition. Persisted to the invoice record — visible on print view and to all roles with read access.
DamageCanvas

Structured field documentation

Field technicians annotate vehicle damage on a structured canvas component during invoice creation. The tool is scoped to tablets and desktop — hidden below the 768px breakpoint where technicians work in a capture-and-move workflow — and visible to office and ownership for documentation review. Annotations persist to the invoice record and render on the print view.

  • ✓Hidden on phones — visible on tablet and desktop
  • ✓Vehicle outline with positional annotation input
  • ✓Persists to invoice — annotations survive status transitions
  • ✓Print-ready at /invoices/[id]/print for physical records
DamageCanvas — structured vehicle damage annotation on invoice creation

DamageCanvas on invoice creation — positional annotation on a vehicle outline, integrated with the invoice record.

Technical Foundation

Built on Supabase with careful client separation

The application separates the service role client from the authenticated user client by design. The base createClient (not the SSR variant) is used for service role operations — preventing the SSR layer from overriding the service role key with a session JWT and silently demoting its permissions. Schema is migration-driven with schema.sql maintained as the authoritative reference.

  • ✓Supabase Auth + PostgREST with RLS on all tables
  • ✓Security-definer get_my_role() — no recursive policy evaluation
  • ✓Explicit GRANT per role — rolbypassrls does not replace table privileges
  • ✓Trigger-enforced state machine + content lock after finalization
  • ✓Migration-driven schema — schema.sql kept in sync
  • ✓Resend delivery with pre-send status validation
Decision Records

What was chosen — and what was ruled out

Supabase Row Level Security over application-layer authorization
Over: Role checks inside API route handlers

Application code can be bypassed — a direct API call, a misconfigured route, or a future code change can skip an application-level check. PostgREST RLS executes at the database before any data is returned, regardless of what the calling code sends. It is not a layer you can accidentally skip.

Database trigger for state machine enforcement
Over: Status validation inside the API route

Triggers fire on every write to the table — direct SQL, an API route, a cron job, a future admin tool. An API route validator only runs when that specific route is called. By moving the state machine into a trigger, the one-way progression rule is guaranteed at the storage layer regardless of how the record is modified.

Base createClient for service role operations
Over: @supabase/ssr createServerClient

The SSR client overrides the service role key with the session JWT when making PostgREST requests — effectively demoting it to the authenticated user's permissions. For operations that need to legitimately bypass RLS (admin tools, email delivery), the base client preserves the service role key and its actual capabilities.