Appearance
Web Application Overview
The Jubiloop web application is a modern React SPA built with TanStack Router, featuring type-safe routing, authentication, and responsive UI components.
Technology Stack
- Framework: React 19 with TypeScript
- Build Tool: Vite
- Routing: TanStack Router (file-based)
- State Management: Zustand
- API Client: TanStack Query
- UI Components: shadcn/ui with Tailwind CSS
- Testing: Vitest and React Testing Library
Architecture Overview
Core Concepts
File-Based Routing
- Routes defined in
/src/routes/directory - Automatic code splitting per route
- Type-safe navigation and parameters
- Routes defined in
Authentication Flow
- Session-based auth with HTTP-only cookies
- Protected route groups:
(auth)vs(public) - Automatic redirects based on auth state
State Management Strategy
- Zustand for auth and global app state
- TanStack Query for server state caching
- Local React state for component UI
API Integration
- Type-safe API client
- Automatic request/response handling
- Optimistic updates for better UX
Project Structure
src/
├── routes/ # TanStack Router file-based routes
│ ├── __root.tsx # Root layout with auth logic
│ ├── index.tsx # Home page (redirects to dashboard)
│ ├── (public)/ # Unauthenticated route group
│ │ ├── sign-in.tsx # Login page
│ │ └── sign-up.tsx # Registration page
│ └── (auth)/ # Authenticated route group
│ └── dashboard/ # Dashboard pages
│ └── route.tsx
├── components/
│ ├── auth/ # Authentication components
│ │ ├── SignInForm.tsx
│ │ └── SignUpForm.tsx
│ └── Header.tsx # Main navigation
├── hooks/
│ └── auth/ # Authentication hooks
│ └── useSession.ts
├── stores/
│ └── authStore.ts # Zustand auth store
├── lib/
│ └── api/
│ └── setup.ts # Central api object — auth-client + Tuyau setup
└── main.tsx # App entry with router setupTanStack Router Configuration
Router Setup (main.tsx)
typescript
const router = createRouter({
routeTree,
context: {
...TanstackQuery.getContext(),
},
defaultPreload: 'intent',
scrollRestoration: true,
defaultStructuralSharing: true,
defaultPreloadStaleTime: 0,
})Route Structure
Root Route (
__root.tsx)- Handles authentication checks
- Provides layout wrapper
- Manages redirects
Public Routes (
(public)/)- Sign in/up pages
- Accessible without auth
- Auto-redirect if already authenticated
Protected Routes (
(auth)/)- Dashboard and app features
- Requires authentication
- Auto-redirect to login if not authenticated
Route Generation
Routes are automatically generated by TanStack Router:
- Source:
/src/routes/directory - Output:
/src/routeTree.gen.ts(auto-generated) - Never edit the generated file directly
Features
Authentication System
Session Management
- HTTP-only cookies for security
- Automatic session refresh
- Remember me functionality
Protected Routes
- Route-level auth checks
- Automatic redirects
- Preserved intended destination
Auth State
- Global auth store (Zustand)
- Persisted user data
- Loading states
Navigation
typescript
// Declarative navigation
import { Link } from '@tanstack/react-router'
<Link to="/dashboard">Dashboard</Link>
// Programmatic navigation
import { useNavigate } from '@tanstack/react-router'
const navigate = useNavigate()
navigate({ to: '/dashboard' })UI/UX
- Responsive design
Performance
- Automatic code splitting per route
- Lazy loading with React.lazy
- Optimistic UI updates
- Request deduplication
Development Workflow
Available Scripts
bash
pnpm run dev # Start dev server (Vite)
pnpm run build # Build for production
pnpm run preview # Preview production build
pnpm run test # Run tests with Vitest
pnpm run lint # Run ESLint
pnpm run lint-fix # Fix ESLint issues
pnpm run format # Format code with Prettier
pnpm run check # Run all checks (lint + format)
pnpm run check-types # TypeScript type checkingPre-commit Hooks
The project includes pre-commit hooks that:
- Enforce Node.js v24+ for development
- Run lint-staged on staged files
- Prevent commits if checks fail
Best Practices
Routing
- Use file-based routing for organization
- Group related routes with folders
- Keep route components focused
State Management
- Zustand is installed but no stores have been implemented yet — do not add Zustand stores without discussing first
- Prefer server state with TanStack Query
- Local state for component-specific UI
Component Design
- Small, focused components
- Composition over complex components
- Proper TypeScript types
Performance
- Implement route-level code splitting
- Use React.memo for expensive components
- Optimize re-renders with proper dependencies
API Integration
The webapp uses two client libraries:
@jubiloop/auth-client— Auth and organization operations (Better Auth + TanStack Query hooks)- Tuyau — End-to-end type-safe client for all other API calls (events, health, etc.), generated from the AdonisJS backend
@jubiloop/logger— Structured logging with adaptor pattern
Webapp Configuration
Located in src/lib/api/setup.ts:
typescript
// api exposes:
// auth.authClient — Better Auth client
// auth.hooks.auth — { useAuth } (sign-in, sign-up, sign-out, session)
// auth.hooks.organization — org management hooks
// auth.auth.getSessionData(queryClient) — cache-first session fetch
// auth.auth.isAuthenticated(data) — Boolean(data?.session && data?.user)Key characteristics:
- Auth via auth-client: Session management, sign-in/up/out, organizations
- General API via Tuyau: Type-safe calls for events, health checks, and all non-auth endpoints
- Query Cache Integration: Both clients work with TanStack Query's cache
TanStack Query Setup
Located in src/integrations/tanstack-query/:
- Query client configuration
- Default stale times
- Cache management
- Global error handling
Data Fetching Pattern
typescript
// Auth/org hooks (from @jubiloop/auth-client)
// General API calls use Tuyau (type-safe, generated from backend)
import { useQuery } from '@tanstack/react-query'
import { auth } from '@/lib/api/setup'
const auth = auth.hooks.auth.useAuth()
const { data: orgs } = auth.hooks.organization.useOrganizations()
const { data, isLoading, error } = useQuery({
queryKey: ['events', orgId],
queryFn: () => tuyau.organizations({ id: orgId }).events.$get(),
})Authentication Implementation
Session Hook
Located in src/hooks/auth/useSession.ts:
- Checks session validity
- Handles token refresh
- Provides loading states
Auth Store
Located in src/stores/authStore.ts:
- User data management
- Login/logout actions
- Persistent state
Protected Route Pattern
typescript
// In __root.tsx
beforeLoad: async ({ context, location }) => {
const session = await checkSession()
if (!session && isProtectedRoute(location)) {
throw redirect({ to: '/sign-in' })
}
}Testing Strategy
Unit Tests
- Test utilities and helpers
- Test custom hooks
- Test store actions
Component Tests
- Test component rendering
- Test user interactions
- Test error states
Integration Tests
- Test auth flows
- Test API interactions
- Test routing behavior
Build & Deployment
The webapp is deployed to Cloudflare Pages:
Build Process
- Vite builds optimized bundles
- Assets are hashed for caching
- Environment variables injected
Deployment
- Automatic deployment on push
- Preview deployments for PRs
- Production deployment on main branch
Environment Variables
Required environment variables:
VITE_API_URL- Backend API URLVITE_APP_URL- Frontend app URLVITE_APP_ENV- Deployment environment (local/development/qa/production/test)
Set in .env.local for development:
bash
VITE_API_URL=http://localhost:3333
VITE_APP_URL=http://localhost:5173Common Tasks
Adding a New Route
- Create route file in
/src/routes/ - Route is auto-discovered by TanStack Router
- Add navigation links as needed
Adding Authentication to a Route
- Place route in
(auth)directory - Route automatically requires authentication
- Unauthenticated users redirected to login
Managing Global State
- Define store slice in
/src/stores/ - Use Zustand's create function
- Import and use in components
Troubleshooting
Route Not Found
- Ensure route file is in
/src/routes/ - Check file naming conventions
- Restart dev server if needed
Authentication Issues
- Check session cookie in browser
- Verify API endpoint is correct
- Check network tab for failed requests
State Not Persisting
- Verify store persistence config
- Check browser storage limits
- Clear storage and retry