DashboardSupportWelcome

👤 USER DOCS

Getting Started

Daily Operations

Shift Workspace & TasksPre-Shift SetupLine-Up CardsShift ReportsForms

Staff & Locations

Staff SchedulingManaging Locations

Oversight

Manager ReportsAnalyticsPre-Shift & Compliance

Incidents & Feedback

Incident ReportingAnonymous FeedbackMessages & Announcements

AI & Settings

AI ChatgearApp Settings

Administration

Dashboard & OnboardingAdmin

⚙️ DEVELOPER DOCS

Getting Started

Getting StartedDevelopmentDeployment Guide

Architecture

Architecture OverviewData FlowArchitecture Decision Records

Core Domain

Core DomainDatabase ReferenceLocations DomainAuth & RBACScheduling DomainReports DomainIncidents DomainNotifications DomainAudit Log & OptimizationDesign Audit Findings

Frontend

Frontend ArchitectureFormsLoading SkeletonsComponentsPWA & NotificationsimageScreenshots

API Reference

API Reference

Endpoints

POS Sales APIOptimization Data APISchedule Shifts APIEmployee Export APIReports APIIncidents APIAI Chat APIPush Notifications APIWebhooks APICron API

Contributing

ContributingcodeCode Examples

Security

Security & Compliance
Danvas IconDanvas
Danvas IconDanvas

Code Examples

Code patterns used in Danvas development

Danvas uses several key patterns for server actions, database queries, and API integrations.

Server Actions

Danvas uses Next.js server actions for form submissions and data mutations.

Basic Form Submission

// apps/app/app/(authenticated)/forms/actions.ts
'use server'

export async function














Contributing

Guidelines for contributing to the Danvas platform

Security & Compliance

Technical design of authentication, authorization, and data protection

On this page

Server ActionsBasic Form SubmissionDatabase Insert with Audit LogDatabase QueriesQuery with FiltersUpsert PatternClerk AuthenticationRequire User AuthRequire AdminZod SchemasForm ValidationSlack NotificationsBuild Block Kit MessageTestingServer Action TestEnvironment VariablesRequired for Development
submitForm
(
formId
:
string
,
data
:
FormData
) {
const { userId, teamId } = await requireUserAuth();
const validated = submitFormSchema.parse({ formId, ...Object.fromEntries(data) });
const submission = await db.insert(formSubmissions).values({
id: generateId(),
formId: validated.formId,
userId,
teamId,
data: validated,
submittedAt: new Date(),
});
await logAudit({ teamId, userId, action: 'form.submitted', targetId: submission.id });
return submission;
}

Database Insert with Audit Log

// Using the audit helper
import { logAudit } from '@repo/database/audit';

await db.insert(incidents).values({
  id: generateId(),
  teamId,
  userId,
  locationId,
  type,
  severity,
  notes,



Database Queries

Query with Filters

// Get submissions with optional filters
const submissions = await db.query.formSubmissions.findMany({
  where: and(
    eq(formSubmissions.teamId, teamId),
    optional(formId ? eq(formSubmissions.formId, formId) : undefined),
    optional








Upsert Pattern

// Push subscription upsert
await db.insert(pushSubscriptions).values({
  userId,
  endpoint: subscription.endpoint,
  p256dh: subscription.keys.p256dh,
  auth: subscription.keys.auth,
}).onConflictDoUpdate({
  target: [pushSubscriptions.userId, pushSubscriptions.endpoint],
  set: {
    p256dh: subscription.keys.p256dh,
    auth: subscription.keys.auth,

Clerk Authentication

Require User Auth

// packages/auth/index.ts
export async function requireUserAuth() {
  const { userId, teamId } = await auth().catch(() => ({ userId: null, teamId: null }));
  if

Require Admin

export async function requireAdmin() {
  const { userId, teamId } = await requireUserAuth();
  const user = await db.query.users.findFirst({
    where: and(



Zod Schemas

Form Validation

// apps/app/app/(authenticated)/forms/schemas.ts
import { z } from 'zod';

export const submitFormSchema = z.object({
  formId: z.string().min(1),
  locationId: z.string().min










Slack Notifications

Build Block Kit Message

// packages/slack/templates/report.ts
export function buildReportBlocks(report: Report, location: Location) {
  return [
    { type: 'header', text: { type: 'plain_text', text: `${location.name






Testing

Server Action Test

// __tests__/reports.test.ts
import { submitReport } from '../actions';

test('submitReport creates report and updates compliance', async () => {
  const mockUser = { userId: 'user_123', teamId: 'team_abc' };
  const






Environment Variables

Required for Development

# apps/app/.env.local
DATABASE_URL=postgresql://user:password@ep-xxx.neon.tech/neondb?sslmode=require
CLERK_SECRET_KEY=sk_test_...
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
OPENROUTER_API_KEY=sk-or-...
CANVAS_TEAM_ID=org_...
createdAt: new Date(),
});
await logAudit({ teamId, userId, action: 'incident.created', targetId: incidentId });
(locationId
?
eq
(formSubmissions.locationId, locationId)
:
undefined
),
),
with: {
form: true,
location: true,
user: true,
},
orderBy: desc(formSubmissions.submittedAt),
limit: 50,
});
},
});
(
!
userId)
throw
new
Error
(
'Unauthorized'
);
return { userId, teamId };
}
eq
(users.clerkId, userId),
eq
(users.teamId, teamId)),
});
if (user?.role !== 'admin') throw new Error('Forbidden');
return { userId, teamId };
}
(
1
),
data: z.record(z.string(), z.any()),
});
export const incidentSchema = z.object({
type: z.enum(['Injury', 'Near Miss', 'Spill / Slip Hazard', 'Complaint', 'Maintenance', 'Stockout']),
severity: z.enum(['low', 'medium', 'high', 'critical']),
date: z.string(),
shift: z.enum(['Breakfast', 'Lunch', 'Dinner', 'Late Night', 'Brunch']),
notes: z.string().max(5000),
mediaUrls: z.array(z.string()).optional(),
});
} — Shift Report`
} },
{ type: 'section', fields: [
{ type: 'mrkdwn', text: `*Sales:* $${report.actualSales} / $${report.goalSales}` },
{ type: 'mrkdwn', text: `*Ratings:* ⭐${report.fohRating} ⭐${report.bohRating}` },
]},
{ type: 'context', elements: [{ type: 'mrkdwn', text: `Submitted via Danvas` }] },
];
}
result
=
await
submitReport
({
...
mockReport, userId: mockUser.userId });
expect(result.id).toBeDefined();
const compliance = await db.query.compliance.findFirst({
where: eq(compliance.reportId, result.id),
});
expect(compliance.filed).toBe(true);
});