Overview
VizBoard follows a modular, feature-based directory structure that separates concerns and promotes code reusability.
Root Directory
vizboard/
├── src/ # Application source code
├── prisma/ # Database schema and migrations
├── public/ # Static assets
├── scripts/ # Database seeding scripts
├── docker-compose.yml # Local database setup
├── package.json # Dependencies and scripts
├── tsconfig.json # TypeScript configuration
├── tailwind.config.ts # Tailwind CSS configuration
├── next.config.js # Next.js configuration
└── .env # Environment variables
Source Directory (src/)
src/
├── app/ # Next.js App Router
├── components/ # React components
├── lib/ # Utilities and helpers
├── hooks/ # Custom React hooks
├── store/ # Zustand state stores
├── types/ # TypeScript type definitions
├── contexts/ # React contexts
└── pages/ # Special Next.js pages
App Directory (src/app/)
Route Structure
The app/ directory uses Next.js 15 App Router with file-based routing:
app/(auth)/
├── layout.tsx # Auth layout (centered forms)
├── login/
│ └── page.tsx # Login page
└── signup/
└── page.tsx # Signup page
Features:
Route group (auth) excludes path from URL
Shared layout for login/signup
Server-side form validation
Redirect after successful auth
app/(homepage)/
├── layout.tsx # Public layout
└── page.tsx # Landing page
Features:
Public-facing homepage
Marketing content
Call-to-action for signup
app/projects/
├── page.tsx # Projects list (Server Component)
├── ProjectsClientPage.tsx # Client-side interactions
└── [projectId]/ # Dynamic project routes
└── dashboard/
├── page.tsx # Dashboard (Server Component)
├── pageClient.tsx # Dashboard client logic
├── DashboardPageWrapper.tsx # Layout wrapper
└── not-found.tsx # 404 handler
Features:
Dynamic routes with [projectId]
Server Components for data fetching
Client Components for interactivity
Nested layouts
app/public/
└── [idPublic]/
├── page.tsx # Public dashboard view
└── not-found.tsx # Invalid share link
Features:
Share dashboards with public URL
No authentication required
Read-only view
app/settings/
└── page.tsx # User settings
Features:
Profile management
Password updates
Account preferences
Server Actions
Server Actions are organized by domain:
Auth Actions app/actions/auth/
└── signUp.ts
User registration
Password hashing
Input validation
Project Actions app/actions/project/
├── crud.ts # CRUD operations
├── connections.ts # Connection management
├── database.ts # Schema introspection
├── validation.ts # Connection validation
├── revalidation.ts # Cache invalidation
└── index.ts # Exports
Create/update/delete projects
Manage connections
Validate credentials
Introspect schemas
Dashboard Actions app/actions/dashboard/
├── crudWidgets.ts # Widget CRUD
├── deleteWidget.ts # Delete handler
├── getTableData.ts # Data fetching
├── updateWidgetOrder.ts # Reordering
├── upsertWidget.ts # Create/update
└── index.ts # Exports
Widget operations
Data queries
Order management
User Actions app/actions/user/
└── user.ts
Profile updates
Password changes
Settings
API Routes
API routes for client-side data fetching:
app/api/
├── auth/
│ └── [...nextauth]/
│ └── route.ts # NextAuth.js handler
├── projects/
│ └── [projectId]/
│ ├── dashboard/
│ │ └── route.ts # GET dashboard data
│ ├── public/
│ │ └── route.ts # GET public dashboard
│ └── widgets/
│ └── order/
│ └── route.ts # PATCH widget order
├── widgets/
│ └── [widgetId]/
│ └── route.ts # GET/PATCH/DELETE widget
├── userprojects/
│ └── route.ts # GET user's projects
├── listconnections/
│ └── route.ts # GET project connections
└── compatible-tables/
└── route.ts # GET compatible table schemas
Route Handlers: // GET /api/projects/[projectId]/dashboard
export async function GET ( request : Request , { params })
// PATCH /api/widgets/[widgetId]
export async function PATCH ( request : Request , { params })
// DELETE /api/widgets/[widgetId]
export async function DELETE ( request : Request , { params })
Root Layout
import { Inter } from "next/font/google"
import { ThemeProvider } from "@/components/theme-provider"
import { Toaster } from "@/components/ui/sonner"
import { SessionProviderContext } from "@/components/providers/session-provider"
export default function RootLayout ({ children }) {
return (
< html lang = "en" suppressHydrationWarning >
< body className = {inter. className } >
< ThemeProvider
attribute = "class"
defaultTheme = "system"
enableSystem
>
< LanguageProvider >
< SessionProviderContext >
{ children }
< Toaster />
</ SessionProviderContext >
</ LanguageProvider >
</ ThemeProvider >
</ body >
</ html >
)
}
Providers:
Theme (light/dark mode)
Session (authentication)
Language (i18n)
Toast notifications
Components Directory (src/components/)
Component Organization
Auth Components components/auth/
├── cardWrapper.tsx
├── login-form.tsx
├── signup-form.tsx
└── oAuths.tsx
Form wrappers
OAuth buttons
Input fields
Project Components components/projects/
├── cards/ # Project cards
├── dialogs/ # Create/edit dialogs
├── forms/ # Project forms
└── lists/ # Project lists
Project cards
Creation forms
Connection forms
Project lists
Dashboard Components components/dashboard/
├── layouts/
│ ├── dashboardLayout.tsx
│ └── PublicDashboardLayout.tsx
├── navigation/
│ ├── appSidebar.tsx
│ └── addWidget.tsx
└── widgets/
├── charts/
├── datatable/
├── integratedDatatable/
├── textblocks/
└── shared/
Dashboard layouts
Sidebar navigation
Widget components
UI Components components/ui/
├── button.tsx
├── dialog.tsx
├── input.tsx
├── select.tsx
├── table.tsx
├── card.tsx
├── tabs.tsx
└── ... (40+ components)
Radix UI wrappers
Styled with Tailwind
Reusable primitives
components/dashboard/widgets/integratedDatatable/
├── integratedTable.tsx # Multi-connection table
├── integratedTableConfig.tsx # Configuration
└── utils/
└── dataFetching.ts # Parallel data fetching
Features:
Compare data from multiple connections
Join data client-side
Unified column mapping
Library Directory (src/lib/)
Authentication
lib/auth/
├── auth.ts # NextAuth.js configuration
└── userSchema.ts # Zod validation schemas
export const { handlers , signIn , signOut , auth } = NextAuth ({
adapter: PrismaAdapter ( prisma ),
session: { strategy: "jwt" },
providers: [ Google , GitHub , Credentials ],
callbacks: { ... },
})
Database
lib/db/
└── prisma.ts # Prisma client singleton
import { PrismaClient } from '@prisma/client'
const globalForPrisma = global as unknown as { prisma : PrismaClient }
const prisma = globalForPrisma . prisma || new PrismaClient ()
if ( process . env . NODE_ENV !== 'production' ) {
globalForPrisma . prisma = prisma
}
export default prisma
Purpose:
Singleton pattern
Prevents multiple instances in development
Connection pooling
Encryption
lib/crypto/
└── crypto.ts # AES-256-GCM encryption
export function encrypt ( text : string ) : string
export function decrypt ( encryptedData : string ) : string
Used for:
Database connection credentials
Sensitive configuration
AES-256-GCM with authentication tags
Project Utilities
lib/projects/
├── schemas/ # Zod validation schemas
│ ├── index.ts
│ ├── project.ts
│ └── connection.ts
└── queries/ # Reusable database queries
Schemas:
Project creation/update validation
Connection validation
Type inference
Adapters
lib/adapters/
└── prisma.ts # Custom Prisma adapter extensions
Other Utilities
Widget Utilities
SWR Config
General Utils
lib/
├── widget.ts # Widget type helpers
├── extractWidgetTypeInfo.ts
└── revalidate.ts # Cache revalidation
Hooks Directory (src/hooks/)
Custom React Hooks
Project Hooks hooks/projects/
├── useProjectsWithStore.ts
├── useProjectConnections.ts
└── index.ts
Fetch projects with SWR
Manage connections
Cache management
Widget Hooks hooks/
├── useWidget.ts
├── useTableDataWithSchema.ts
└── useIntegratedTableData.ts
Widget data fetching
Table data with caching
Multi-connection queries
UI Hooks hooks/
├── use-mobile.ts
└── useOnlineStatus.ts
Mobile detection
Network status
Responsive behavior
Example: useProjectsWithStore
import useSWR from 'swr'
import { useProjectsStore } from '@/store/projects'
export function useProjectsWithStore () {
const { data , error , isLoading , mutate } = useSWR (
'/api/userprojects' ,
fetcher
)
const { setProjects } = useProjectsStore ()
useEffect (() => {
if ( data ?. projects ) {
setProjects ( data . projects )
}
}, [ data ])
return {
projects: data ?. projects || [],
isLoading ,
error ,
refresh: mutate ,
}
}
Store Directory (src/store/)
Zustand State Stores
Dashboard Stores store/dashboard/
├── widgetsStore.ts
├── widgetDialogStore.ts
└── index.ts
Widget list state
Dialog open/close
Editing widget
Project Stores store/projects/
├── projectsStore.ts
├── projectDraftStore.ts
└── index.ts
Projects list
Draft persistence
Form state
Example: widgetDialogStore.ts
Types Directory (src/types/)
TypeScript Definitions
types/
├── auth.d.ts # NextAuth.js type extensions
├── widget.ts # Widget type definitions
├── database.ts # Database schema types
└── api.ts # API response types
Example: NextAuth Type Extensions
import 'next-auth'
declare module 'next-auth' {
interface User {
id : string
firstName ?: string
lastName ?: string
password ?: string | null
}
interface Session {
user : {
id : string
email ?: string
name ?: string
firstName ?: string
lastName ?: string
}
}
}
declare module 'next-auth/jwt' {
interface JWT {
id ?: string
firstName ?: string
lastName ?: string
}
}
Prisma Directory
Database Schema & Migrations
prisma/
├── schema.prisma # Database schema definition
├── migrations/ # Migration history
│ ├── 20240101_init/
│ ├── 20240105_add_widgets/
│ └── migration_lock.toml
└── seed.ts # Database seeding script
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env ( "DATABASE_URL" )
}
model User {
id String @id @default ( uuid ())
firstName String ?
lastName String ?
email String @unique
password String ?
projects Project []
accounts Account []
@@map ( "users" )
}
model Project {
id String @id @default ( uuid ())
userId String
title String
description String ?
isPublic Boolean @default ( false )
idPublic String ? @unique
dbconnections DbConnection []
widgets Widget []
orderedWidgetIds String []
user User @relation ( ... )
@@map ( "projects" )
}
model DbConnection {
id String @id @default ( uuid ())
projectId String
title String
dbAccess Json # Encrypted credentials
dbSchema Json ? # Cached schema
isValid Boolean ?
validationError String ?
lastIntrospectionAt DateTime ?
project Project @relation ( ... )
@@map ( "dbconnections" )
}
model Widget {
id String @id @default ( uuid ())
projectId String
title String
description String ?
type String # bar, line, area, datatable, etc.
subtype String ? # Simple, integrated, etc.
configs Json ? # Widget-specific config
project Project @relation ( ... )
@@map ( "widgets" )
}
Key Features:
UUID primary keys
Cascade deletes
JSON for flexible configs
Encrypted credentials
Schema caching
Scripts Directory
Database Seeding Scripts
scripts/
├── seed_store.js # Seed store data (test DB 1 & 2)
├── seed_clients.js # Seed clients (test DB 3)
├── seed_commandes.js # Seed orders (test DB 3)
└── seed_apirequestevent.js # Seed API events (test DB 3)
These scripts populate external PostgreSQL test databases with sample data for widget testing: # Seed all test databases
npm run seed:test-dbs
# Or individual databases
npm run seed:store # Products, users, sales, reviews
npm run seed:clients # Client records
npm run seed:commandes # Order records
Generated Data:
100-1000 realistic records
Uses Faker.js for names, dates, etc.
Relational data (foreign keys)
Time-series data for charts
Configuration Files
Next.js Configuration
next.config.js
tsconfig.json
tailwind.config.ts
module . exports = {
reactStrictMode: true ,
experimental: {
serverActions: true ,
},
images: {
domains: [ 'lh3.googleusercontent.com' , 'avatars.githubusercontent.com' ],
},
}
Import Aliases
VizBoard uses TypeScript path aliases for cleaner imports:
// Instead of:
import { Button } from '../../../components/ui/button'
// Use:
import { Button } from '@/components/ui/button'
Configuration:
{
"compilerOptions" : {
"paths" : {
"@/*" : [ "./src/*" ]
}
}
}
File Naming Conventions
React Components PascalCase
Button.tsx
DashboardLayout.tsx
ProjectCard.tsx
Utilities & Hooks camelCase
utils.ts
useProjects.ts
swrConfig.ts
Server Actions camelCase
crud.ts
signUp.ts
getTableData.ts
Route Files Next.js Convention
page.tsx (route)
layout.tsx (layout)
route.ts (API route)
not-found.tsx (404)
Code Organization Principles
Server Components - Data fetching and rendering
Client Components - Interactivity and state
Server Actions - Data mutations
API Routes - Client-side data fetching
Hooks - Reusable logic
Stores - Global state
Components - Reusable UI
Feature-Based Organization
projects/
├── components/projects/ # Project-specific UI
├── actions/project/ # Project mutations
├── hooks/projects/ # Project data hooks
└── store/projects/ # Project state
Benefits:
Easy to find related code
Clear dependencies
Better modularity
Scalable structure
Keep related files close together: widgets/
├── charts/
│ ├── barChart.tsx # Component
│ ├── chartConfig.tsx # Config dialog
│ └── utils/ # Chart-specific utils
│ ├── aggregation.ts
│ └── formatting.ts
Best Practices
Key Guidelines:
Server Components by default - Only add "use client" when needed
Colocation - Keep related files together
Type safety - Use TypeScript everywhere
Validation - Zod schemas for all inputs
Error handling - Try-catch in Server Actions
Revalidation - Invalidate cache after mutations
Security - Encrypt sensitive data
Performance - Optimize database queries
Growing the Codebase
When adding new features:
New widget type? → Add to components/dashboard/widgets/
New data operation? → Add Server Action to app/actions/
New route? → Add to app/ with appropriate route group
Reusable logic? → Create custom hook in hooks/
Global state? → Add Zustand store in store/
New utility? → Add to lib/
Database change? → Update Prisma schema and migrate
The structure is designed to scale from small teams to large applications while maintaining clarity and organization.