Tanksafe Internal

Welcome to TankSafe Docs

Secure internal guidance and playbooks for the TankSafe team.

Sign in with your organisation Google account to continue.

Access is limited to authorised Google Workspace users.

Skip to main content
Tank Safe Solutions DocsDocumentationUser GuidesTechnical Guides
GitHub
  • TankSafe Overview
  • User Guides
    • Getting Started
    • Dashboard & Global Search
    • Account Management & Security
    • Running Inspections
    • Inspector Job List
    • admin
    • inspections
  • Technical Guides
    • Platform Architecture
    • Development & Deployment Workflow
    • Local Development Setup
    • Integrations
  • Changelog
  • contributing
  • overview
  • Technical Guides
  • Platform Architecture

Platform Architecture

This architecture snapshot reflects the code that exists in the tank-wise-portal repository today. It omits aspirational services that do not yet exist in source control.

High-level view​

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ GitHub (tank-wise-portal│◄───── Feature / PR branches β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ CI triggers β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Vercel Preview Deploys │────►│ Vercel Production Deploy β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ TankSafe Portal β”‚ β”‚ Supabase project β”‚
β”‚ β€’ React (Vite) β”‚ β”‚ β€’ Auth (email/password) β”‚
β”‚ β€’ TypeScript β”‚ β”‚ β€’ Postgres tables β”‚
β”‚ β€’ Tailwind + Radix UI β”‚ β”‚ β€’ Storage & edge functionsβ”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

There is no separate API gateway or worker tier. React components call Supabase directly from the browser.

Front-end structure​

  • Entry points – src/main.tsx bootstraps React Router and wraps the app in providers (QueryClientProvider, AuthProvider, TooltipProvider).
  • Routing – src/App.tsx declares all routes. ProtectedRoute guards authenticated areas and enforces administrator-only routes via the adminOnly prop.
  • Layout – AppLayout renders the navigation shell (sidebar, header, toast containers) used across the authenticated UI.
  • Styling – Tailwind CSS with utility classes in JSX, plus shared styles in src/index.css and src/App.css.
  • State – React hooks and React Query handle most state. useSharedWizardFields keeps cross-step data in sync inside the inspection wizard.
src/App.tsx
const App = () => (
<QueryClientProvider client={queryClient}>
<AuthProvider>
<TooltipProvider>
<Toaster />
<Sonner />
<BrowserRouter>
<Routes>
<Route path="/auth" element={<Auth />} />
<Route
path="/"
element={
<ProtectedRoute>
<AppLayout>
<Index />
</AppLayout>
</ProtectedRoute>
}
/>
{/* additional routes trimmed for brevity */}
</Routes>
</BrowserRouter>
</TooltipProvider>
</AuthProvider>
</QueryClientProvider>
);

Supabase integration​

  • Auth – Supabase email/password authentication with optional TOTP MFA. useAuth.tsx wraps session handling, profile loading, and MFA state.
  • Data – The app queries Supabase tables directly (e.g., inspections, form_responses, inspection_checklist_items, continuity_test_readings). Helper utilities in src/lib/api.ts, src/lib/gridConfigs.ts, and src/lib/formSchemas.ts centralise repetitive logic.
  • Edge functions – Located under supabase/functions/admin-*. These functions perform privileged user-management tasks (invite, update, delete, list MFA status). No other server-side automation is present.
  • Storage – Thickness test drawings save annotated images to Supabase Storage via URLs captured in thickness_test_records. There is no general asset pipeline beyond this use case.
src/hooks/useAuth.tsx
export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const [session, setSession] = useState<Session | null>(null);
const [profile, setProfile] = useState<Profile | null>(null);
const [hasVerifiedMfa, setHasVerifiedMfa] = useState(false);

useEffect(() => {
const applySession = async (session: Session | null) => {
if (session?.user) {
setSession(session);
setUser(session.user);
const userProfile = await fetchProfile(session.user.id);
setProfile(userProfile);
await evaluateMfaStatus();
return;
}
setSession(null);
setUser(null);
setProfile(null);
setHasVerifiedMfa(false);
setRequiresMfaSetup(false);
};

const { data: { subscription } } = supabase.auth.onAuthStateChange((event, session) => {
if (event === 'SIGNED_OUT') {
setSession(null);
setUser(null);
setProfile(null);
setIsLoading(false);
return;
}
applySession(session ?? null);
});

supabase.auth.getSession().then(({ data: { session } }) => applySession(session ?? null));
return () => subscription.unsubscribe();
}, []);

return (
<AuthContext.Provider value={{ user, session, profile, hasVerifiedMfa, /* … */ }}>
{children}
</AuthContext.Provider>
);
}
supabase/functions/admin-invite-user/index.ts
serve(async (req) => {
if (req.method !== "POST") {
return new Response(JSON.stringify({ error: "Method not allowed" }), { status: 405, headers: corsHeaders });
}

const supabaseAdmin = createClient(supabaseUrl, serviceRoleKey, {
auth: { persistSession: false, autoRefreshToken: false },
});

const inviteResult = await supabaseAdmin.auth.admin.inviteUserByEmail(email, {
data: { full_name },
});

if (role === "inspector") {
const { data: existingInspector } = await supabaseAdmin
.from("inspectors")
.select("id")
.eq("user_id", userId)
.maybeSingle();

if (!existingInspector) {
await supabaseAdmin.from("inspectors").insert({
user_id: userId,
name: full_name,
});
}
}

return new Response(JSON.stringify({ message: "Invite sent", userId, role }), {
status: 200,
headers: corsHeaders,
});
});

Key modules​

  • InspectionWizard.tsx – Implements the multi-step inspection workflow defined by the WIZARD_STEPS constant. Relies on JsonForm, GridTable, ContinuityMatrix, and ThicknessTestCanvas components.
  • AdminJobDetail.tsx – Aggregates job metadata, form responses, checklist items, continuity readings, and charge sheet data into a dossier-style view.
  • AdminJobEdit.tsx – Provides full edit access to inspection details and supporting tables using shared form/grid components.
  • AdminSchedule.tsx – Uses react-big-calendar with drag-and-drop to manage inspection schedules, inspector availability, and reminders.
  • AdminUsers.tsx – Orchestrates edge function calls for inviting, updating, deleting users, and displaying MFA status.
src/pages/InspectionWizard.tsx
const WIZARD_STEPS = [
{
key: 'risk',
title: "Risk Assessment",
subtitle: "Permit to Work",
templateId: TEMPLATE_IDS.RISK_ASSESSMENT,
hasGrid: true,
gridTable: 'risk_assessment_compartments',
},
{
key: 'vt_header',
title: "Vapour Tightness",
subtitle: "Header & Page 1",
templateId: TEMPLATE_IDS.VAPOUR_TIGHTNESS,
hasGrid: true,
gridTable: 'vapour_tightness_compartments',
},
// …additional steps omitted for brevity
];

const InspectionWizard = () => {
const { job } = useParams();
const [currentStep, setCurrentStep] = useState(0);
const currentStepConfig = WIZARD_STEPS[currentStep];

const defaultValues = useMemo<JsonObject>(() => {
const savedData = formData[currentStepConfig.templateId] ?? {};
return {
...initialFormDefaults,
...sharedData,
...Object.fromEntries(Object.entries(savedData).filter(([, value]) => isMeaningfulValue(value))),
};
}, [currentStepConfig, formData, initialFormDefaults, sharedData]);

// …wizard logic continues
};

Database overview​

Refer to the SQL migrations under supabase/migrations for the authoritative schema. The UI actively works with these tables:

  • inspections – Core job/inspection record (status, scheduling, operator snapshot, inspector assignment).
  • form_responses – Stores JSON form data keyed by template_id.
  • inspection_checklist_items – Individual checklist entries linked to an inspection.
  • continuity_test_readings – Electrical continuity measurements per compartment.
  • charge_sheet_work_items, charge_sheet_parts, charge_sheet_labour – Billing data summarised in Admin views.
  • thickness_test_records – Stores image URLs for annotated thickness diagrams.
  • inspectors, profiles, inspector_availability, schedule_reminders – Support assignment and scheduling workflows.

Row-level security policies are managed in Supabase and enforced automatically via the Supabase client; there is no custom policy bypass logic in the portal.

src/lib/types.ts
export interface Inspection {
id: string;
job_number: string;
inspection_number: string | null;
status: InspectionStatus;
operator_id: string | null;
inspector_id: string | null;
notes: string | null;
created_at: string;
updated_at: string;
}

export interface FormResponse {
id: string;
inspection_id: string;
template_id: string;
answers: JsonObject;
completed_at: string | null;
created_at: string;
}

Extensibility points​

  • Add new wizard steps by extending WIZARD_STEPS and updating FORM_SCHEMAS/GRID_CONFIGS accordingly.
  • To introduce new admin tables, create the Supabase schema migration, extend query logic inside AdminJobDetail.tsx or AdminJobEdit.tsx, and update helper utilities as needed.
  • Edge functions can be added under supabase/functions. The front-end invokes them through supabase.functions.invoke just as the admin user functions do today.

Deployment surface​

  • Source control lives in GitHub. Day-to-day commits target the shared development branch; releases merge development into main via pull request.
  • Vercel automatically builds every push to development, providing preview URLs for QA and stakeholder review. Merging to main promotes the build to production without manual intervention.
  • The docs site (docusaurus/tanksafe-docs) follows the same GitHub/Vercel pairing so product and documentation stay in sync.
  • Supabase environment variables (URL, publishable key, service role key) are managed in Vercel project settings. Keep preview and production values aligned before merging.

Keep this page updated whenever the repository adds new architectural pieces so the docs remain accurate. For a deeper dive into branch strategy, preview builds, and production rollouts, see Development & Deployment Workflow.

Tags:
  • architecture
Edit this page
Previous
Technical Guides
Next
Development & Deployment Workflow
  • High-level view
  • Front-end structure
  • Supabase integration
  • Key modules
  • Database overview
  • Extensibility points
  • Deployment surface
Docs
  • Overview
Community
  • Support
  • Roadmap
  • Status Page
More
  • TankSafe.io
  • GitHub
Copyright Β© 2025 Tank Safe Solutions. Built with Docusaurus.