Dashboard System
The Hoffnungsträger Sprachbrücke platform features a role-based dashboard system with different interfaces for interpreters and requesters.
Overview
File: apps/web/app/dashboard/page.tsx
The dashboard provides:
- ✅ Role-based content (Interpreter vs Requester)
- ✅ Authentication protection
- ✅ User profile management
- ✅ Quick actions and statistics
- ✅ Responsive design
User Roles
Interpreter Dashboard
Role: interpreter
Features:
- 📋 Active assignments overview
- 📅 Calendar integration
- 💰 Earnings tracking
- 🔍 Available orders search
- 👤 Profile management
Quick Stats:
- Active Orders: Number of currently assigned orders
- This Month Earnings: Financial tracking
- Completed Orders: Historical performance
- Average Rating: Client feedback scores
Quick Actions:
- View Available Orders
- Update Availability
- Submit Travel Costs
- View Earnings Report
Requester Dashboard
Role: requester (client)
Features:
- 📝 Order request management
- 📊 Request status tracking
- 💳 Billing overview
- 🏢 Organization settings
- 📋 Order history
Quick Stats:
- Pending Requests: Open order requests
- Active Orders: Currently in progress
- This Month Costs: Budget tracking
- Completed Orders: Service history
Quick Actions:
- Create New Request
- View Active Orders
- Download Invoices
- Manage Organization
Authentication Flow
Protected Route
'use client'
import { useEffect, useState } from 'react'
import { useRouter } from 'next/navigation'
import { auth, User } from '@/lib/api'
export default function Dashboard() {
const [user, setUser] = useState<User | null>(null)
const [isLoading, setIsLoading] = useState(true)
const router = useRouter()
useEffect(() => {
const checkAuth = async () => {
try {
if (!auth.isAuthenticated()) {
router.push('/login')
return
}
const profile = await auth.getProfile()
setUser(profile)
} catch (error) {
console.error('Authentication failed:', error)
router.push('/login')
} finally {
setIsLoading(false)
}
}
checkAuth()
}, [router])
if (isLoading) {
return <LoadingSpinner />
}
if (!user) {
return null // Redirecting...
}
// Role-based rendering
const isInterpreter = user.roles?.some(role => role.name === 'interpreter')
return isInterpreter ? (
<InterpreterDashboard user={user} />
) : (
<RequesterDashboard user={user} />
)
}
Role Detection
User Roles Array
Laravel uses Spatie roles/permissions. Users can have multiple roles:
interface User {
roles: Array<{
id: number
name: string // 'interpreter', 'requester', 'admin'
guardName: string
createdAt: string
updatedAt: string
}>
}
// Role checking utility
const hasRole = (user: User, roleName: string): boolean => {
return user.roles?.some(role => role.name === roleName) ?? false
}
// Usage
const isInterpreter = hasRole(user, 'interpreter')
const isRequester = hasRole(user, 'requester')
const isAdmin = hasRole(user, 'admin')
Dashboard Components
Header Integration
File: apps/web/components/header.tsx
// Dynamic header based on authentication
{user ? (
<div className="flex items-center space-x-4">
<span className="text-gray-700">
Willkommen, {user.firstName}!
</span>
<Button
variant="outline"
onClick={() => router.push('/dashboard')}
>
Dashboard
</Button>
<Button
variant="outline"
onClick={handleLogout}
>
Abmelden
</Button>
</div>
) : (
// Login/Register buttons
)}
Responsive Layout
// Mobile-first responsive design
<div className="min-h-screen bg-gray-50">
<div className="container mx-auto px-4 py-8">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Welcome Section */}
<div className="lg:col-span-3">
<WelcomeCard user={user} />
</div>
{/* Quick Stats */}
<div className="lg:col-span-2">
<StatsGrid stats={roleBasedStats} />
</div>
{/* Quick Actions */}
<div className="lg:col-span-1">
<QuickActionsCard actions={roleBasedActions} />
</div>
{/* Main Content */}
<div className="lg:col-span-3">
{isInterpreter ? (
<InterpreterContent />
) : (
<RequesterContent />
)}
</div>
</div>
</div>
</div>
Data Management
State Management
const [stats, setStats] = useState({
activeOrders: 0,
earnings: 0,
completedOrders: 0,
rating: 0
})
const [recentActivity, setRecentActivity] = useState([])
const [quickActions, setQuickActions] = useState([])
// Load dashboard data
useEffect(() => {
const loadDashboardData = async () => {
try {
// Parallel API calls for performance
const [statsData, activityData] = await Promise.all([
fetchUserStats(user.id),
fetchRecentActivity(user.id)
])
setStats(statsData)
setRecentActivity(activityData)
} catch (error) {
console.error('Failed to load dashboard data:', error)
}
}
if (user) {
loadDashboardData()
}
}, [user])
API Integration
// Dashboard-specific API calls
const fetchUserStats = async (userId: string) => {
const response = await fetch(`/api/users/${userId}/stats`, {
headers: {
'Authorization': `Bearer ${auth.getToken()}`
}
})
return response.json()
}
const fetchRecentActivity = async (userId: string) => {
const response = await fetch(`/api/users/${userId}/activity`, {
headers: {
'Authorization': `Bearer ${auth.getToken()}`
}
})
return response.json()
}
UI Components
Stats Cards
interface StatCard {
title: string
value: string | number
change?: string
trend?: 'up' | 'down' | 'neutral'
icon: React.ComponentType
}
const StatsGrid = ({ stats }: { stats: StatCard[] }) => (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{stats.map((stat, index) => (
<Card key={index} className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">
{stat.title}
</p>
<p className="text-2xl font-bold text-gray-900">
{stat.value}
</p>
{stat.change && (
<p className={`text-sm ${
stat.trend === 'up' ? 'text-green-600' :
stat.trend === 'down' ? 'text-red-600' :
'text-gray-600'
}`}>
{stat.change}
</p>
)}
</div>
<stat.icon className="h-8 w-8 text-gray-400" />
</div>
</Card>
))}
</div>
)
Quick Actions
interface QuickAction {
title: string
description: string
href?: string
onClick?: () => void
icon: React.ComponentType
variant: 'primary' | 'secondary' | 'outline'
}
const QuickActionsCard = ({ actions }: { actions: QuickAction[] }) => (
<Card className="p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">
Schnellaktionen
</h3>
<div className="space-y-3">
{actions.map((action, index) => (
<Button
key={index}
variant={action.variant}
className="w-full justify-start"
onClick={action.onClick}
>
<action.icon className="mr-2 h-4 w-4" />
{action.title}
</Button>
))}
</div>
</Card>
)
Navigation Integration
Breadcrumbs
const DashboardBreadcrumbs = ({ user }: { user: User }) => {
const role = user.roles?.[0]?.name || 'user'
return (
<nav className="flex mb-6" aria-label="Breadcrumb">
<ol className="flex items-center space-x-2">
<li>
<Link href="/" className="text-gray-500 hover:text-gray-700">
Home
</Link>
</li>
<li>
<span className="text-gray-400">/</span>
</li>
<li>
<span className="text-gray-900 font-medium">
{role === 'interpreter' ? 'Sprachmittler' : 'Auftraggeber'} Dashboard
</span>
</li>
</ol>
</nav>
)
}
Future Enhancements
- 📱 Progressive Web App: Mobile app-like experience
- 🔔 Real-time Notifications: WebSocket integration
- 📊 Advanced Analytics: Charts and reporting
- 🌙 Dark Mode: Theme switching
- 🌍 Multi-language: i18n support
- 🔒 Two-Factor Auth: Enhanced security
Security Considerations
- Route Protection: All dashboard routes require authentication
- Role Validation: Server-side role checking
- Token Refresh: Automatic token renewal
- CSRF Protection: Laravel Sanctum integration
- XSS Prevention: Input sanitization