Environment Variables
The web dashboard requires configuration through environment variables. Create a .env.local file in the web/ directory:
# Supabase Configuration (Required)
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key-here
# Optional: Custom Configuration
NEXT_PUBLIC_APP_NAME="Incidents App"
NEXT_PUBLIC_SUPPORT_EMAIL=[email protected]
All variables prefixed with NEXT_PUBLIC_ are exposed to the browser. Never use the Supabase service_role key in client-side code.
Required Variables
Your Supabase project URLGet this from: Supabase Dashboard → Settings → API → Project URL
NEXT_PUBLIC_SUPABASE_ANON_KEY
Your Supabase anonymous/public API keyGet this from: Supabase Dashboard → Settings → API → Project API keys → anon public
Optional Variables
NEXT_PUBLIC_APP_NAME
string
default:"Incidents App"
Application name displayed in the dashboard header
NEXT_PUBLIC_SUPPORT_EMAIL
Support email address for user assistance
Next.js Configuration
The dashboard’s Next.js configuration is defined in next.config.ts:
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
// Enable React strict mode for better development experience
reactStrictMode: true,
// Configure image domains for external images
images: {
domains: ['your-supabase-project.supabase.co'],
},
// Experimental features
experimental: {
// Enable server actions
serverActions: true,
},
};
export default nextConfig;
Image Configuration
If you plan to display user avatars or incident attachments from Supabase Storage:
images: {
domains: [
'your-project.supabase.co',
// Add other image CDN domains
],
formats: ['image/avif', 'image/webp'],
}
Supabase Client Configuration
The dashboard uses separate Supabase clients for client and server components:
Client-Side Configuration
import { createBrowserClient } from '@supabase/ssr';
export function createClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
}
Usage in client components:
'use client';
import { createClient } from '@/lib/supabase';
import { useEffect, useState } from 'react';
export function MyClientComponent() {
const supabase = createClient();
const [data, setData] = useState(null);
useEffect(() => {
async function loadData() {
const { data } = await supabase
.from('incidents')
.select('*');
setData(data);
}
loadData();
}, []);
return <div>{/* Render data */}</div>;
}
Server-Side Configuration
import { createServerClient } from '@supabase/ssr';
import { cookies } from 'next/headers';
export async function createClient() {
const cookieStore = await cookies();
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get(name: string) {
return cookieStore.get(name)?.value;
},
},
}
);
}
Usage in server components:
import { createClient } from '@/lib/supabase-server';
export default async function ServerComponent() {
const supabase = await createClient();
const { data: incidents } = await supabase
.from('incidents')
.select('*');
return <div>{/* Render incidents */}</div>;
}
Middleware Configuration
The middleware protects dashboard routes from unauthorized access:
import { NextRequest, NextResponse } from "next/server";
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
if (pathname.startsWith("/dashboard")) {
// Check for Supabase auth cookie
const hasSession = Array.from(request.cookies.getAll()).some(
(c) => c.name.startsWith("sb-") && c.name.endsWith("-auth-token")
);
if (!hasSession) {
return NextResponse.redirect(new URL("/", request.url));
}
}
return NextResponse.next();
}
export const config = {
matcher: ["/dashboard/:path*"],
};
Customizing Protected Routes
To protect additional routes, update the matcher:
export const config = {
matcher: [
"/dashboard/:path*",
"/admin/:path*",
"/settings/:path*"
],
};
Tailwind CSS Configuration
The dashboard uses Tailwind CSS v4 with custom design tokens:
import type { Config } from 'tailwindcss';
const config: Config = {
content: [
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
colors: {
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))',
},
// ... more color tokens
},
},
},
plugins: [],
};
export default config;
shadcn/ui Configuration
The dashboard uses shadcn/ui components. Configuration is in components.json:
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "app/globals.css",
"baseColor": "slate",
"cssVariables": true
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}
Adding Components
Add new shadcn/ui components with the CLI:
pnpm dlx shadcn add button
pnpm dlx shadcn add card
TypeScript Configuration
The project uses strict TypeScript:
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
Theme Configuration
The dashboard supports light and dark modes through CSS variables defined in app/globals.css:
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
/* ... more variables */
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--primary: 210 40% 98%;
/* ... more variables */
}
}
Toggle theme in components:
import { useTheme } from 'next-themes';
export function ThemeToggle() {
const { theme, setTheme } = useTheme();
return (
<button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
Toggle theme
</button>
);
}
Database Configuration
Database schema and Row Level Security policies must be configured in Supabase. See Supabase Setup for details.
Next Steps
Analytics
Explore analytics features
Incident Management
Manage incidents in the dashboard
User Management
Configure employee accounts
Deployment
Deploy to production