shortcut-next 0.2.2 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -2
- package/templates/base/@core/configs/clientConfig.ts +1 -1
- package/templates/base/@core/context/AuthContext.tsx +21 -28
- package/templates/base/@core/hooks/useAbility.ts +58 -0
- package/templates/base/app/(dashboard)/dashboard/page.tsx +104 -0
- package/templates/base/app/(dashboard)/layout.tsx +97 -0
- package/templates/base/app/home/page.tsx +112 -0
- package/templates/base/app/login/page.tsx +296 -0
- package/templates/base/app/unauthorized/page.tsx +120 -0
- package/templates/base/components/MSWProvider.tsx +54 -0
- package/templates/base/components/auth/LoginForm.tsx +279 -0
- package/templates/base/components/auth/SignupForm.tsx +348 -0
- package/templates/base/components/loaders/Spinner.tsx +5 -24
- package/templates/base/components/ui/ErrorMessage.tsx +17 -0
- package/templates/base/components/ui/FormFieldWrapper.tsx +27 -0
- package/templates/base/docs/AuthorizationDocumentation.md +348 -0
- package/templates/base/lib/abilities/checkAuthorization.ts +74 -0
- package/templates/base/lib/abilities/index.ts +27 -0
- package/templates/base/lib/abilities/roles.ts +75 -0
- package/templates/base/lib/abilities/routeMap.ts +35 -0
- package/templates/base/lib/abilities/routeMatcher.ts +117 -0
- package/templates/base/lib/abilities/types.ts +68 -0
- package/templates/base/lib/mocks/browser.ts +11 -0
- package/templates/base/lib/mocks/db.ts +124 -0
- package/templates/base/lib/mocks/handlers/auth.ts +203 -0
- package/templates/base/lib/mocks/handlers/index.ts +16 -0
- package/templates/base/lib/mocks/index.ts +34 -0
- package/templates/base/lib/mocks/jwt.ts +99 -0
- package/templates/base/middleware.ts +147 -0
- package/templates/base/package-lock.json +725 -2
- package/templates/base/package.json +13 -2
- package/templates/base/providers/AppProviders.tsx +8 -5
- package/templates/base/public/locales/ar.json +73 -0
- package/templates/base/public/locales/en.json +73 -0
- package/templates/base/public/mockServiceWorker.js +349 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shortcut-next",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "Scaffold Next.js apps with MUI base or Tailwind v4 preset.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -61,6 +61,9 @@
|
|
|
61
61
|
"ora": "^8.2.0"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
|
-
"@
|
|
64
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
65
|
+
"@semantic-release/git": "^10.0.1",
|
|
66
|
+
"@types/js-cookie": "^3.0.6",
|
|
67
|
+
"semantic-release": "^25.0.3"
|
|
65
68
|
}
|
|
66
69
|
}
|
|
@@ -75,11 +75,9 @@ export const AuthProvider = ({ children }: AuthProviderProps) => {
|
|
|
75
75
|
try {
|
|
76
76
|
setIsLoading(true)
|
|
77
77
|
|
|
78
|
-
const response = await axios.post<AuthResponse>(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
{ timeout: authConfig.requestTimeout }
|
|
82
|
-
)
|
|
78
|
+
const response = await axios.post<AuthResponse>(`${authConfig.baseURL}${authConfig.loginEndpoint}`, credentials, {
|
|
79
|
+
timeout: authConfig.requestTimeout
|
|
80
|
+
})
|
|
83
81
|
|
|
84
82
|
const { user: userData, accessToken, refreshToken } = response.data
|
|
85
83
|
|
|
@@ -98,12 +96,12 @@ export const AuthProvider = ({ children }: AuthProviderProps) => {
|
|
|
98
96
|
|
|
99
97
|
// Redirect to home page
|
|
100
98
|
router.push(authConfig.homePageURL)
|
|
101
|
-
|
|
102
99
|
} catch (error) {
|
|
103
100
|
console.error('Login failed:', error)
|
|
104
|
-
const message =
|
|
105
|
-
|
|
106
|
-
|
|
101
|
+
const message =
|
|
102
|
+
axios.isAxiosError(error) && error.response?.data?.message
|
|
103
|
+
? error.response.data.message
|
|
104
|
+
: 'Login failed. Please try again.'
|
|
107
105
|
|
|
108
106
|
onError?.(message)
|
|
109
107
|
} finally {
|
|
@@ -139,12 +137,12 @@ export const AuthProvider = ({ children }: AuthProviderProps) => {
|
|
|
139
137
|
|
|
140
138
|
// Redirect to home page
|
|
141
139
|
router.push(authConfig.homePageURL)
|
|
142
|
-
|
|
143
140
|
} catch (error) {
|
|
144
141
|
console.error('Signup failed:', error)
|
|
145
|
-
const message =
|
|
146
|
-
|
|
147
|
-
|
|
142
|
+
const message =
|
|
143
|
+
axios.isAxiosError(error) && error.response?.data?.message
|
|
144
|
+
? error.response.data.message
|
|
145
|
+
: 'Signup failed. Please try again.'
|
|
148
146
|
|
|
149
147
|
onError?.(message)
|
|
150
148
|
} finally {
|
|
@@ -154,18 +152,17 @@ export const AuthProvider = ({ children }: AuthProviderProps) => {
|
|
|
154
152
|
|
|
155
153
|
// ** Logout function
|
|
156
154
|
const logout = async (): Promise<void> => {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
// Clear cookie
|
|
164
|
-
document.cookie = `${authConfig.cookieName}=; path=/; expires=Thu, 01 Jan 1970 00:00:01 GMT`
|
|
155
|
+
// Clear all auth data regardless of API call result
|
|
156
|
+
setUser(null)
|
|
157
|
+
localStorage.removeItem(authConfig.storageTokenKeyName)
|
|
158
|
+
localStorage.removeItem(authConfig.storageUserDataKeyName)
|
|
159
|
+
localStorage.removeItem(authConfig.storageRefreshTokenKeyName)
|
|
165
160
|
|
|
166
|
-
|
|
167
|
-
|
|
161
|
+
// Clear cookie
|
|
162
|
+
document.cookie = `${authConfig.cookieName}=; path=/; expires=Thu, 01 Jan 1970 00:00:01 GMT`
|
|
168
163
|
|
|
164
|
+
// Redirect to login page
|
|
165
|
+
router.push(authConfig.loginPageURL)
|
|
169
166
|
}
|
|
170
167
|
|
|
171
168
|
// ** Context value
|
|
@@ -180,11 +177,7 @@ export const AuthProvider = ({ children }: AuthProviderProps) => {
|
|
|
180
177
|
setLoading: setIsLoading
|
|
181
178
|
}
|
|
182
179
|
|
|
183
|
-
return
|
|
184
|
-
<AuthContext.Provider value={contextValue}>
|
|
185
|
-
{children}
|
|
186
|
-
</AuthContext.Provider>
|
|
187
|
-
)
|
|
180
|
+
return <AuthContext.Provider value={contextValue}>{children}</AuthContext.Provider>
|
|
188
181
|
}
|
|
189
182
|
|
|
190
183
|
// ** Auth Hook
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useMemo } from 'react'
|
|
4
|
+
import { useAuth } from '@/@core/context/AuthContext'
|
|
5
|
+
import { defineAbilitiesFor } from '@/lib/abilities'
|
|
6
|
+
import type { AppAbility, UserRole } from '@/lib/abilities'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Hook to get CASL ability instance for the current user
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* ```tsx
|
|
13
|
+
* const ability = useAbility()
|
|
14
|
+
*
|
|
15
|
+
* if (ability.can('read', 'Users')) {
|
|
16
|
+
* // Show users link
|
|
17
|
+
* }
|
|
18
|
+
*
|
|
19
|
+
* if (ability.can('manage', 'Settings')) {
|
|
20
|
+
* // Show settings link
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @returns CASL AppAbility instance
|
|
25
|
+
*/
|
|
26
|
+
export function useAbility(): AppAbility {
|
|
27
|
+
const { user } = useAuth()
|
|
28
|
+
|
|
29
|
+
const ability = useMemo(() => {
|
|
30
|
+
const role = (user?.role as UserRole) || 'viewer'
|
|
31
|
+
return defineAbilitiesFor(role)
|
|
32
|
+
}, [user?.role])
|
|
33
|
+
|
|
34
|
+
return ability
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Hook to check if current user can perform an action
|
|
39
|
+
*
|
|
40
|
+
* Convenience wrapper around useAbility for simple permission checks.
|
|
41
|
+
*
|
|
42
|
+
* Usage:
|
|
43
|
+
* ```tsx
|
|
44
|
+
* const canReadUsers = useCan('read', 'Users')
|
|
45
|
+
* const canManageSettings = useCan('manage', 'Settings')
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* @param action - The action to check
|
|
49
|
+
* @param subject - The subject to check against
|
|
50
|
+
* @returns boolean indicating if the user can perform the action
|
|
51
|
+
*/
|
|
52
|
+
export function useCan(
|
|
53
|
+
action: 'read' | 'create' | 'update' | 'delete' | 'manage',
|
|
54
|
+
subject: 'Dashboard' | 'Users' | 'Settings' | 'Reports' | 'Tickets' | 'all'
|
|
55
|
+
): boolean {
|
|
56
|
+
const ability = useAbility()
|
|
57
|
+
return ability.can(action, subject)
|
|
58
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Box, Container, Typography, Grid, Card, CardContent } from '@mui/material'
|
|
4
|
+
import { Users, Ticket, Settings, BarChart3 } from 'lucide-react'
|
|
5
|
+
import Link from 'next/link'
|
|
6
|
+
|
|
7
|
+
const dashboardCards = [
|
|
8
|
+
{
|
|
9
|
+
title: 'Users',
|
|
10
|
+
description: 'Manage system users',
|
|
11
|
+
icon: Users,
|
|
12
|
+
href: '/dashboard/users',
|
|
13
|
+
color: 'primary',
|
|
14
|
+
roles: ['admin', 'manager']
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
title: 'Tickets',
|
|
18
|
+
description: 'View and manage tickets',
|
|
19
|
+
icon: Ticket,
|
|
20
|
+
href: '/dashboard/tickets',
|
|
21
|
+
color: 'secondary',
|
|
22
|
+
roles: ['admin', 'manager', 'agent']
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
title: 'Reports',
|
|
26
|
+
description: 'View analytics and reports',
|
|
27
|
+
icon: BarChart3,
|
|
28
|
+
href: '/dashboard/reports',
|
|
29
|
+
color: 'info',
|
|
30
|
+
roles: ['admin', 'manager', 'agent', 'viewer']
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
title: 'Settings',
|
|
34
|
+
description: 'Configure application settings',
|
|
35
|
+
icon: Settings,
|
|
36
|
+
href: '/dashboard/settings',
|
|
37
|
+
color: 'warning',
|
|
38
|
+
roles: ['admin']
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Dashboard Home Page
|
|
44
|
+
*
|
|
45
|
+
* This page is accessible to all authenticated users.
|
|
46
|
+
* The navigation cards shown here are just examples -
|
|
47
|
+
* actual access control is enforced by middleware.
|
|
48
|
+
*/
|
|
49
|
+
export default function DashboardPage() {
|
|
50
|
+
return (
|
|
51
|
+
<Container maxWidth='lg' sx={{ py: 4 }}>
|
|
52
|
+
<Typography variant='h4' fontWeight={700} gutterBottom>
|
|
53
|
+
Dashboard
|
|
54
|
+
</Typography>
|
|
55
|
+
<Typography variant='body1' color='text.secondary' sx={{ mb: 4 }}>
|
|
56
|
+
Welcome to your dashboard. Select a section to get started.
|
|
57
|
+
</Typography>
|
|
58
|
+
|
|
59
|
+
<Grid container spacing={3}>
|
|
60
|
+
{dashboardCards.map(card => (
|
|
61
|
+
<Grid size={{ xs: 12, sm: 6, md: 3 }} key={card.title}>
|
|
62
|
+
<Card
|
|
63
|
+
component={Link}
|
|
64
|
+
href={card.href}
|
|
65
|
+
sx={{
|
|
66
|
+
height: '100%',
|
|
67
|
+
textDecoration: 'none',
|
|
68
|
+
transition: 'transform 0.2s, box-shadow 0.2s',
|
|
69
|
+
'&:hover': {
|
|
70
|
+
transform: 'translateY(-4px)',
|
|
71
|
+
boxShadow: 4
|
|
72
|
+
}
|
|
73
|
+
}}
|
|
74
|
+
>
|
|
75
|
+
<CardContent sx={{ textAlign: 'center', py: 4 }}>
|
|
76
|
+
<Box
|
|
77
|
+
sx={{
|
|
78
|
+
width: 56,
|
|
79
|
+
height: 56,
|
|
80
|
+
borderRadius: 2,
|
|
81
|
+
bgcolor: `${card.color}.lighter`,
|
|
82
|
+
display: 'flex',
|
|
83
|
+
alignItems: 'center',
|
|
84
|
+
justifyContent: 'center',
|
|
85
|
+
mx: 'auto',
|
|
86
|
+
mb: 2
|
|
87
|
+
}}
|
|
88
|
+
>
|
|
89
|
+
<card.icon size={28} />
|
|
90
|
+
</Box>
|
|
91
|
+
<Typography variant='h6' fontWeight={600}>
|
|
92
|
+
{card.title}
|
|
93
|
+
</Typography>
|
|
94
|
+
<Typography variant='body2' color='text.secondary'>
|
|
95
|
+
{card.description}
|
|
96
|
+
</Typography>
|
|
97
|
+
</CardContent>
|
|
98
|
+
</Card>
|
|
99
|
+
</Grid>
|
|
100
|
+
))}
|
|
101
|
+
</Grid>
|
|
102
|
+
</Container>
|
|
103
|
+
)
|
|
104
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { cookies } from 'next/headers'
|
|
2
|
+
import { redirect } from 'next/navigation'
|
|
3
|
+
import { decodeJwt } from 'jose'
|
|
4
|
+
import type { UserRole } from '@/lib/abilities'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* JWT payload structure
|
|
8
|
+
*/
|
|
9
|
+
interface JWTPayload {
|
|
10
|
+
sub: string
|
|
11
|
+
email?: string
|
|
12
|
+
role?: string
|
|
13
|
+
name?: string
|
|
14
|
+
exp?: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Session data extracted from JWT
|
|
19
|
+
*/
|
|
20
|
+
interface Session {
|
|
21
|
+
userId: string
|
|
22
|
+
role: UserRole
|
|
23
|
+
name?: string
|
|
24
|
+
email?: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Extract session from cookies (server-side)
|
|
29
|
+
*
|
|
30
|
+
* This is the defense-in-depth check that runs in addition to middleware.
|
|
31
|
+
* It ensures no protected content is ever rendered without a valid session.
|
|
32
|
+
*/
|
|
33
|
+
async function getServerSession(): Promise<Session | null> {
|
|
34
|
+
const cookieStore = await cookies()
|
|
35
|
+
const token = cookieStore.get('accessToken')?.value
|
|
36
|
+
|
|
37
|
+
if (!token) {
|
|
38
|
+
return null
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const payload = decodeJwt(token) as JWTPayload
|
|
43
|
+
|
|
44
|
+
// Check expiration
|
|
45
|
+
if (payload.exp && payload.exp * 1000 < Date.now()) {
|
|
46
|
+
return null
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const validRoles: UserRole[] = ['admin', 'manager', 'agent', 'viewer']
|
|
50
|
+
const role = validRoles.includes(payload.role as UserRole)
|
|
51
|
+
? (payload.role as UserRole)
|
|
52
|
+
: 'viewer'
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
userId: payload.sub,
|
|
56
|
+
role,
|
|
57
|
+
name: payload.name,
|
|
58
|
+
email: payload.email,
|
|
59
|
+
}
|
|
60
|
+
} catch {
|
|
61
|
+
return null
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Dashboard Layout
|
|
67
|
+
*
|
|
68
|
+
* This is a Server Component that provides defense-in-depth for all dashboard routes.
|
|
69
|
+
* Even if middleware is bypassed, this layout ensures no protected content renders
|
|
70
|
+
* without a valid session.
|
|
71
|
+
*
|
|
72
|
+
* The layout wraps all routes in the (dashboard) route group.
|
|
73
|
+
*/
|
|
74
|
+
export default async function DashboardLayout({
|
|
75
|
+
children,
|
|
76
|
+
}: {
|
|
77
|
+
children: React.ReactNode
|
|
78
|
+
}) {
|
|
79
|
+
const session = await getServerSession()
|
|
80
|
+
|
|
81
|
+
// Defense-in-depth: redirect if no session
|
|
82
|
+
// This should rarely trigger since middleware handles it first
|
|
83
|
+
if (!session) {
|
|
84
|
+
redirect('/login?returnUrl=/dashboard')
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<div className="dashboard-layout">
|
|
89
|
+
{/*
|
|
90
|
+
Add dashboard navigation/sidebar here
|
|
91
|
+
Navigation items can be filtered based on session.role
|
|
92
|
+
using the useAbility hook in client components
|
|
93
|
+
*/}
|
|
94
|
+
<main className="dashboard-content">{children}</main>
|
|
95
|
+
</div>
|
|
96
|
+
)
|
|
97
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Container, Typography, Paper, Box, Chip, Button } from '@mui/material'
|
|
4
|
+
import { LogOut } from 'lucide-react'
|
|
5
|
+
import { useAuth } from '@/@core/context/AuthContext'
|
|
6
|
+
import { useCan } from '@/@core/hooks/useAbility'
|
|
7
|
+
|
|
8
|
+
// Role-specific home components
|
|
9
|
+
function AdminHome() {
|
|
10
|
+
return (
|
|
11
|
+
<Paper sx={{ p: 4, bgcolor: 'error.50', border: '2px solid', borderColor: 'error.main' }}>
|
|
12
|
+
<Typography variant='h5' color='error.main' gutterBottom>
|
|
13
|
+
Admin Dashboard
|
|
14
|
+
</Typography>
|
|
15
|
+
<Typography variant='body1' color='text.secondary'>
|
|
16
|
+
Welcome, Admin! You have full access to all system features including user management, settings, reports, and
|
|
17
|
+
tickets.
|
|
18
|
+
</Typography>
|
|
19
|
+
</Paper>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function ManagerHome() {
|
|
24
|
+
return (
|
|
25
|
+
<Paper sx={{ p: 4, bgcolor: 'warning.50', border: '2px solid', borderColor: 'warning.main' }}>
|
|
26
|
+
<Typography variant='h5' color='warning.main' gutterBottom>
|
|
27
|
+
Manager Dashboard
|
|
28
|
+
</Typography>
|
|
29
|
+
<Typography variant='body1' color='text.secondary'>
|
|
30
|
+
Welcome, Manager! You can manage users, tickets, and reports. Settings are restricted to admins.
|
|
31
|
+
</Typography>
|
|
32
|
+
</Paper>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function AgentHome() {
|
|
37
|
+
return (
|
|
38
|
+
<Paper sx={{ p: 4, bgcolor: 'info.50', border: '2px solid', borderColor: 'info.main' }}>
|
|
39
|
+
<Typography variant='h5' color='info.main' gutterBottom>
|
|
40
|
+
Agent Dashboard
|
|
41
|
+
</Typography>
|
|
42
|
+
<Typography variant='body1' color='text.secondary'>
|
|
43
|
+
Welcome, Agent! You can handle tickets and view reports. User management and settings are restricted.
|
|
44
|
+
</Typography>
|
|
45
|
+
</Paper>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function ViewerHome() {
|
|
50
|
+
return (
|
|
51
|
+
<Paper sx={{ p: 4, bgcolor: 'success.50', border: '2px solid', borderColor: 'success.main' }}>
|
|
52
|
+
<Typography variant='h5' color='success.main' gutterBottom>
|
|
53
|
+
Viewer Dashboard
|
|
54
|
+
</Typography>
|
|
55
|
+
<Typography variant='body1' color='text.secondary'>
|
|
56
|
+
Welcome, Viewer! You have read-only access to the dashboard and reports.
|
|
57
|
+
</Typography>
|
|
58
|
+
</Paper>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export default function HomePage() {
|
|
63
|
+
const { user, logout } = useAuth()
|
|
64
|
+
|
|
65
|
+
// Use CASL abilities to determine which component to render
|
|
66
|
+
// Each check is based on unique permissions for that role
|
|
67
|
+
const canManageSettings = useCan('manage', 'Settings') // Only admin
|
|
68
|
+
const canManageUsers = useCan('manage', 'Users') // Admin & Manager
|
|
69
|
+
const canManageTickets = useCan('manage', 'Tickets') // Admin, Manager & Agent
|
|
70
|
+
|
|
71
|
+
// Determine the appropriate component based on abilities (most privileged first)
|
|
72
|
+
const getRoleInfo = () => {
|
|
73
|
+
if (canManageSettings) {
|
|
74
|
+
return { Component: AdminHome, label: 'ADMIN', color: 'error' as const }
|
|
75
|
+
}
|
|
76
|
+
if (canManageUsers) {
|
|
77
|
+
return { Component: ManagerHome, label: 'MANAGER', color: 'warning' as const }
|
|
78
|
+
}
|
|
79
|
+
if (canManageTickets) {
|
|
80
|
+
return { Component: AgentHome, label: 'AGENT', color: 'info' as const }
|
|
81
|
+
}
|
|
82
|
+
return { Component: ViewerHome, label: 'VIEWER', color: 'success' as const }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const { Component: RoleComponent, label, color } = getRoleInfo()
|
|
86
|
+
|
|
87
|
+
const handleLogout = () => {
|
|
88
|
+
logout()
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<Container maxWidth='md' sx={{ py: 6 }}>
|
|
93
|
+
<Box sx={{ mb: 4, textAlign: 'center' }}>
|
|
94
|
+
<Typography variant='h3' gutterBottom>
|
|
95
|
+
Welcome Home
|
|
96
|
+
</Typography>
|
|
97
|
+
<Typography variant='body1' color='text.secondary' sx={{ mb: 2 }}>
|
|
98
|
+
Hello, {user?.name || 'User'}!
|
|
99
|
+
</Typography>
|
|
100
|
+
<Chip label={label} color={color} size='medium' />
|
|
101
|
+
</Box>
|
|
102
|
+
|
|
103
|
+
<RoleComponent />
|
|
104
|
+
|
|
105
|
+
<Box sx={{ mt: 4, textAlign: 'center' }}>
|
|
106
|
+
<Button variant='outlined' color='error' startIcon={<LogOut size={18} />} onClick={handleLogout}>
|
|
107
|
+
Logout
|
|
108
|
+
</Button>
|
|
109
|
+
</Box>
|
|
110
|
+
</Container>
|
|
111
|
+
)
|
|
112
|
+
}
|