blacksmith-cli 0.1.6 → 0.1.7
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/dist/index.js +1745 -689
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/src/templates/frontend/package.json.hbs +4 -4
- package/src/templates/frontend/src/__tests__/test-utils.tsx.hbs +5 -4
- package/src/templates/frontend/src/app.tsx.hbs +13 -9
- package/src/templates/frontend/src/features/auth/adapter.ts.hbs +7 -7
- package/src/templates/frontend/src/features/auth/components/auth-provider.tsx.hbs +91 -11
- package/src/templates/frontend/src/features/auth/hooks/use-auth.ts.hbs +3 -4
- package/src/templates/frontend/src/features/auth/pages/forgot-password-page.tsx.hbs +76 -12
- package/src/templates/frontend/src/features/auth/pages/login-page.tsx.hbs +84 -11
- package/src/templates/frontend/src/features/auth/pages/register-page.tsx.hbs +85 -14
- package/src/templates/frontend/src/features/auth/pages/reset-password-page.tsx.hbs +63 -12
- package/src/templates/frontend/src/features/auth/types.ts.hbs +32 -0
- package/src/templates/frontend/src/pages/dashboard/components/quick-start-card.tsx.hbs +19 -18
- package/src/templates/frontend/src/pages/dashboard/components/stack-cards.tsx.hbs +33 -31
- package/src/templates/frontend/src/pages/dashboard/components/welcome-header.tsx.hbs +5 -5
- package/src/templates/frontend/src/pages/dashboard/dashboard.tsx.hbs +5 -5
- package/src/templates/frontend/src/pages/home/home.tsx.hbs +48 -52
- package/src/templates/frontend/src/router/auth-guard.tsx.hbs +10 -7
- package/src/templates/frontend/src/router/error-boundary.tsx.hbs +16 -12
- package/src/templates/frontend/src/router/layouts/auth-layout.tsx.hbs +12 -12
- package/src/templates/frontend/src/router/layouts/main-layout.tsx.hbs +62 -55
- package/src/templates/frontend/src/shared/components/loading-spinner.tsx.hbs +6 -6
- package/src/templates/frontend/src/shared/components/not-found-page.tsx.hbs +1 -1
- package/src/templates/frontend/src/shared/hooks/use-debounce.ts.hbs +18 -2
- package/src/templates/frontend/src/styles/globals.css.hbs +3 -1
- package/src/templates/frontend/tailwind.config.js.hbs +1 -1
- package/src/templates/resource/frontend/components/{{kebab}}-form.tsx.hbs +3 -2
- package/src/templates/resource/frontend/pages/{{kebabs}}-page.tsx.hbs +3 -2
- package/src/templates/resource/frontend/pages/{{kebab}}-detail-page.tsx.hbs +5 -3
- package/src/templates/resource/pages/components/{{kebab}}-form.tsx.hbs +3 -2
- package/src/templates/resource/pages/{{kebabs}}-page.tsx.hbs +3 -2
- package/src/templates/resource/pages/{{kebab}}-detail-page.tsx.hbs +5 -3
|
@@ -1,26 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Reset Password Page
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Password reset form built with Chakra UI.
|
|
5
5
|
* Generated by Blacksmith. You own this file — customize as needed.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { useState } from 'react'
|
|
9
|
+
import {
|
|
10
|
+
Box,
|
|
11
|
+
Button,
|
|
12
|
+
FormControl,
|
|
13
|
+
FormLabel,
|
|
14
|
+
Input,
|
|
15
|
+
VStack,
|
|
16
|
+
Heading,
|
|
17
|
+
Text,
|
|
18
|
+
Alert,
|
|
19
|
+
AlertIcon,
|
|
20
|
+
AlertDescription,
|
|
21
|
+
Link as ChakraLink,
|
|
22
|
+
} from '@chakra-ui/react'
|
|
9
23
|
import { useNavigate, useParams } from 'react-router-dom'
|
|
10
24
|
import { useAuth } from '../hooks/use-auth'
|
|
11
|
-
import { useState } from 'react'
|
|
12
25
|
import { Path } from '@/router/paths'
|
|
13
26
|
|
|
14
27
|
export default function ResetPasswordPage() {
|
|
15
28
|
const navigate = useNavigate()
|
|
16
29
|
const { token } = useParams<{ token: string }>()
|
|
17
30
|
const { confirmPasswordReset, error } = useAuth()
|
|
31
|
+
|
|
32
|
+
const [password, setPassword] = useState('')
|
|
18
33
|
const [loading, setLoading] = useState(false)
|
|
19
34
|
|
|
20
|
-
const handleSubmit = async (
|
|
35
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
36
|
+
e.preventDefault()
|
|
21
37
|
setLoading(true)
|
|
22
38
|
try {
|
|
23
|
-
const result = await confirmPasswordReset(
|
|
39
|
+
const result = await confirmPasswordReset(token || '', password)
|
|
24
40
|
if (result.success) {
|
|
25
41
|
navigate(Path.Login, { replace: true })
|
|
26
42
|
}
|
|
@@ -30,12 +46,47 @@ export default function ResetPasswordPage() {
|
|
|
30
46
|
}
|
|
31
47
|
|
|
32
48
|
return (
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
49
|
+
<Box>
|
|
50
|
+
<VStack spacing={2} mb={6} align="start">
|
|
51
|
+
<Heading size="lg">Reset Password</Heading>
|
|
52
|
+
<Text color="gray.500">Enter your new password below.</Text>
|
|
53
|
+
</VStack>
|
|
54
|
+
|
|
55
|
+
{error && (
|
|
56
|
+
<Alert status="error" borderRadius="md" mb={4}>
|
|
57
|
+
<AlertIcon />
|
|
58
|
+
<AlertDescription>{error.message}</AlertDescription>
|
|
59
|
+
</Alert>
|
|
60
|
+
)}
|
|
61
|
+
|
|
62
|
+
<form onSubmit={handleSubmit}>
|
|
63
|
+
<VStack spacing={4}>
|
|
64
|
+
<FormControl isRequired>
|
|
65
|
+
<FormLabel>New Password</FormLabel>
|
|
66
|
+
<Input
|
|
67
|
+
type="password"
|
|
68
|
+
value={password}
|
|
69
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
70
|
+
placeholder="Choose a new password"
|
|
71
|
+
/>
|
|
72
|
+
</FormControl>
|
|
73
|
+
|
|
74
|
+
<Button
|
|
75
|
+
type="submit"
|
|
76
|
+
colorScheme="blue"
|
|
77
|
+
width="full"
|
|
78
|
+
isLoading={loading}
|
|
79
|
+
>
|
|
80
|
+
Reset Password
|
|
81
|
+
</Button>
|
|
82
|
+
</VStack>
|
|
83
|
+
</form>
|
|
84
|
+
|
|
85
|
+
<Text fontSize="sm" color="gray.500" mt={6} textAlign="center">
|
|
86
|
+
<ChakraLink color="blue.500" onClick={() => navigate(Path.Login)}>
|
|
87
|
+
Back to Sign In
|
|
88
|
+
</ChakraLink>
|
|
89
|
+
</Text>
|
|
90
|
+
</Box>
|
|
40
91
|
)
|
|
41
92
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface AuthUser {
|
|
2
|
+
id: string
|
|
3
|
+
email: string
|
|
4
|
+
displayName: string | null
|
|
5
|
+
photoURL: string | null
|
|
6
|
+
emailVerified: boolean
|
|
7
|
+
providerId: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface AuthError {
|
|
11
|
+
code: string
|
|
12
|
+
message: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface AuthResult {
|
|
16
|
+
success: boolean
|
|
17
|
+
user?: AuthUser
|
|
18
|
+
error?: AuthError
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type SocialProvider = 'google' | 'github' | 'facebook' | 'apple'
|
|
22
|
+
|
|
23
|
+
export interface AuthAdapter {
|
|
24
|
+
signInWithEmail(email: string, password: string): Promise<AuthResult>
|
|
25
|
+
signUpWithEmail(email: string, password: string, displayName?: string): Promise<AuthResult>
|
|
26
|
+
signInWithSocial(provider: SocialProvider): Promise<AuthResult>
|
|
27
|
+
sendPasswordResetEmail(email: string): Promise<{ success: boolean; error?: AuthError }>
|
|
28
|
+
confirmPasswordReset(code: string, newPassword: string): Promise<{ success: boolean; error?: AuthError }>
|
|
29
|
+
signOut(): Promise<void>
|
|
30
|
+
getCurrentUser(): AuthUser | null
|
|
31
|
+
onAuthStateChanged(callback: (user: AuthUser | null) => void): () => void
|
|
32
|
+
}
|
|
@@ -1,36 +1,37 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Card,
|
|
3
3
|
CardHeader,
|
|
4
|
-
|
|
5
|
-
CardDescription,
|
|
6
|
-
CardContent,
|
|
4
|
+
CardBody,
|
|
7
5
|
Badge,
|
|
8
|
-
|
|
6
|
+
HStack,
|
|
9
7
|
Text,
|
|
10
|
-
|
|
8
|
+
Heading,
|
|
9
|
+
} from '@chakra-ui/react'
|
|
11
10
|
import { Rocket, Terminal } from 'lucide-react'
|
|
12
11
|
|
|
13
12
|
export function QuickStartCard() {
|
|
14
13
|
return (
|
|
15
14
|
<Card>
|
|
16
|
-
<CardHeader
|
|
17
|
-
<
|
|
18
|
-
<
|
|
15
|
+
<CardHeader pb={3}>
|
|
16
|
+
<HStack justify="space-between">
|
|
17
|
+
<Heading size="sm">Quick Start</Heading>
|
|
19
18
|
<Badge variant="outline">
|
|
20
|
-
<
|
|
21
|
-
|
|
19
|
+
<HStack spacing={1}>
|
|
20
|
+
<Rocket className="h-3 w-3" />
|
|
21
|
+
<span>Guide</span>
|
|
22
|
+
</HStack>
|
|
22
23
|
</Badge>
|
|
23
|
-
</
|
|
24
|
+
</HStack>
|
|
24
25
|
</CardHeader>
|
|
25
|
-
<
|
|
26
|
-
<
|
|
26
|
+
<CardBody>
|
|
27
|
+
<Text fontSize="sm" color="gray.500">
|
|
27
28
|
Scaffold a new resource with models, API, and pages in one command.
|
|
28
|
-
</
|
|
29
|
-
<
|
|
29
|
+
</Text>
|
|
30
|
+
<HStack spacing={2} className="mt-4 rounded-lg bg-muted px-3 py-2">
|
|
30
31
|
<Terminal className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
|
|
31
|
-
<
|
|
32
|
-
</
|
|
33
|
-
</
|
|
32
|
+
<code className="font-mono text-sm">blacksmith make:resource Post</code>
|
|
33
|
+
</HStack>
|
|
34
|
+
</CardBody>
|
|
34
35
|
</Card>
|
|
35
36
|
)
|
|
36
37
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Card,
|
|
3
3
|
CardHeader,
|
|
4
|
-
|
|
5
|
-
CardDescription,
|
|
6
|
-
CardContent,
|
|
4
|
+
CardBody,
|
|
7
5
|
Badge,
|
|
8
6
|
Button,
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
HStack,
|
|
8
|
+
Heading,
|
|
9
|
+
Text,
|
|
10
|
+
} from '@chakra-ui/react'
|
|
11
11
|
import { Database, Layout } from 'lucide-react'
|
|
12
12
|
import { Link } from 'react-router-dom'
|
|
13
13
|
import { Path } from '@/router/paths'
|
|
@@ -17,27 +17,27 @@ const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000'
|
|
|
17
17
|
export function BackendCard() {
|
|
18
18
|
return (
|
|
19
19
|
<Card>
|
|
20
|
-
<CardHeader
|
|
21
|
-
<
|
|
22
|
-
<
|
|
20
|
+
<CardHeader pb={3}>
|
|
21
|
+
<HStack justify="space-between">
|
|
22
|
+
<Heading size="sm">Backend</Heading>
|
|
23
23
|
<Badge variant="outline">
|
|
24
|
-
<
|
|
25
|
-
|
|
24
|
+
<HStack spacing={1}>
|
|
25
|
+
<Database className="h-3 w-3" />
|
|
26
|
+
<span>Django</span>
|
|
27
|
+
</HStack>
|
|
26
28
|
</Badge>
|
|
27
|
-
</
|
|
29
|
+
</HStack>
|
|
28
30
|
</CardHeader>
|
|
29
|
-
<
|
|
30
|
-
<
|
|
31
|
+
<CardBody>
|
|
32
|
+
<Text fontSize="sm" color="gray.500">
|
|
31
33
|
Django REST API with JWT auth, serializers, and auto-generated OpenAPI docs.
|
|
32
|
-
</
|
|
34
|
+
</Text>
|
|
33
35
|
<div className="mt-4">
|
|
34
|
-
<Button variant="outline" size="sm"
|
|
35
|
-
|
|
36
|
-
View API Docs
|
|
37
|
-
</a>
|
|
36
|
+
<Button variant="outline" size="sm" as="a" href={`${API_URL}/api/docs/`} target="_blank" rel="noopener noreferrer">
|
|
37
|
+
View API Docs
|
|
38
38
|
</Button>
|
|
39
39
|
</div>
|
|
40
|
-
</
|
|
40
|
+
</CardBody>
|
|
41
41
|
</Card>
|
|
42
42
|
)
|
|
43
43
|
}
|
|
@@ -45,25 +45,27 @@ export function BackendCard() {
|
|
|
45
45
|
export function FrontendCard() {
|
|
46
46
|
return (
|
|
47
47
|
<Card>
|
|
48
|
-
<CardHeader
|
|
49
|
-
<
|
|
50
|
-
<
|
|
48
|
+
<CardHeader pb={3}>
|
|
49
|
+
<HStack justify="space-between">
|
|
50
|
+
<Heading size="sm">Frontend</Heading>
|
|
51
51
|
<Badge variant="outline">
|
|
52
|
-
<
|
|
53
|
-
|
|
52
|
+
<HStack spacing={1}>
|
|
53
|
+
<Layout className="h-3 w-3" />
|
|
54
|
+
<span>React</span>
|
|
55
|
+
</HStack>
|
|
54
56
|
</Badge>
|
|
55
|
-
</
|
|
57
|
+
</HStack>
|
|
56
58
|
</CardHeader>
|
|
57
|
-
<
|
|
58
|
-
<
|
|
59
|
+
<CardBody>
|
|
60
|
+
<Text fontSize="sm" color="gray.500">
|
|
59
61
|
React + TypeScript with TanStack Query, React Router, and Tailwind CSS.
|
|
60
|
-
</
|
|
62
|
+
</Text>
|
|
61
63
|
<div className="mt-4">
|
|
62
|
-
<Button variant="outline" size="sm"
|
|
63
|
-
|
|
64
|
+
<Button variant="outline" size="sm" as={Link} to={Path.Home}>
|
|
65
|
+
Home Page
|
|
64
66
|
</Button>
|
|
65
67
|
</div>
|
|
66
|
-
</
|
|
68
|
+
</CardBody>
|
|
67
69
|
</Card>
|
|
68
70
|
)
|
|
69
71
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { VStack, Heading, Text } from '@chakra-ui/react'
|
|
2
2
|
|
|
3
3
|
interface WelcomeHeaderProps {
|
|
4
4
|
displayName: string
|
|
@@ -6,9 +6,9 @@ interface WelcomeHeaderProps {
|
|
|
6
6
|
|
|
7
7
|
export function WelcomeHeader({ displayName }: WelcomeHeaderProps) {
|
|
8
8
|
return (
|
|
9
|
-
<
|
|
10
|
-
<
|
|
11
|
-
<Text color="
|
|
12
|
-
</
|
|
9
|
+
<VStack spacing={1} align="stretch">
|
|
10
|
+
<Heading size="lg">Dashboard</Heading>
|
|
11
|
+
<Text color="gray.500">Welcome back, {displayName}.</Text>
|
|
12
|
+
</VStack>
|
|
13
13
|
)
|
|
14
14
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { VStack, Divider, SimpleGrid } from '@chakra-ui/react'
|
|
2
2
|
import { useAuth } from '@/features/auth/hooks/use-auth'
|
|
3
3
|
import { WelcomeHeader } from './components/welcome-header'
|
|
4
4
|
import { QuickStartCard } from './components/quick-start-card'
|
|
@@ -8,14 +8,14 @@ export default function DashboardPage() {
|
|
|
8
8
|
const { user } = useAuth()
|
|
9
9
|
|
|
10
10
|
return (
|
|
11
|
-
<
|
|
11
|
+
<VStack spacing={8} align="stretch">
|
|
12
12
|
<WelcomeHeader displayName={user?.displayName || user?.email || 'there'} />
|
|
13
13
|
<Divider />
|
|
14
|
-
<
|
|
14
|
+
<SimpleGrid columns=\{{ base: 1, sm: 2, lg: 3 }} spacing={4}>
|
|
15
15
|
<QuickStartCard />
|
|
16
16
|
<BackendCard />
|
|
17
17
|
<FrontendCard />
|
|
18
|
-
</
|
|
19
|
-
</
|
|
18
|
+
</SimpleGrid>
|
|
19
|
+
</VStack>
|
|
20
20
|
)
|
|
21
21
|
}
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
VStack,
|
|
3
|
+
HStack,
|
|
4
|
+
Heading,
|
|
5
5
|
Text,
|
|
6
6
|
Button,
|
|
7
7
|
Card,
|
|
8
8
|
CardHeader,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
Grid,
|
|
13
|
-
} from '@blacksmith-ui/react'
|
|
9
|
+
CardBody,
|
|
10
|
+
SimpleGrid,
|
|
11
|
+
} from '@chakra-ui/react'
|
|
14
12
|
import { useAuth } from '@/features/auth/hooks/use-auth'
|
|
15
13
|
import { Link } from 'react-router-dom'
|
|
16
14
|
import { Path } from '@/router/paths'
|
|
@@ -22,56 +20,54 @@ export default function HomePage() {
|
|
|
22
20
|
const { isAuthenticated } = useAuth()
|
|
23
21
|
|
|
24
22
|
return (
|
|
25
|
-
<
|
|
26
|
-
<
|
|
27
|
-
<
|
|
23
|
+
<VStack spacing={10} align="stretch" className="pt-16 pb-12">
|
|
24
|
+
<VStack spacing={4} align="center">
|
|
25
|
+
<Heading as="h1" size="2xl" className="sm:text-5xl">
|
|
28
26
|
{{projectName}}
|
|
29
|
-
</
|
|
30
|
-
<Text
|
|
27
|
+
</Heading>
|
|
28
|
+
<Text fontSize="lg" color="gray.500" textAlign="center" className="max-w-md">
|
|
31
29
|
Your app is ready. Sign in to get started or explore the API.
|
|
32
30
|
</Text>
|
|
33
|
-
<
|
|
31
|
+
<HStack spacing={4} className="pt-2">
|
|
34
32
|
{isAuthenticated ? (
|
|
35
|
-
<Button size="lg"
|
|
36
|
-
|
|
33
|
+
<Button size="lg" as={Link} to={Path.Dashboard}>
|
|
34
|
+
Dashboard
|
|
37
35
|
</Button>
|
|
38
36
|
) : (
|
|
39
|
-
<Button size="lg"
|
|
40
|
-
|
|
37
|
+
<Button size="lg" as={Link} to={Path.Login}>
|
|
38
|
+
Sign In
|
|
41
39
|
</Button>
|
|
42
40
|
)}
|
|
43
|
-
<Button variant="outline" size="lg"
|
|
44
|
-
|
|
45
|
-
API Docs
|
|
46
|
-
</a>
|
|
41
|
+
<Button variant="outline" size="lg" as="a" href={`${API_URL}/api/docs/`} target="_blank" rel="noopener noreferrer">
|
|
42
|
+
API Docs
|
|
47
43
|
</Button>
|
|
48
|
-
</
|
|
49
|
-
</
|
|
44
|
+
</HStack>
|
|
45
|
+
</VStack>
|
|
50
46
|
|
|
51
|
-
<
|
|
47
|
+
<SimpleGrid columns=\{{ base: 1, md: 3 }} spacing={4} className="max-w-3xl mx-auto w-full">
|
|
52
48
|
<Card>
|
|
53
|
-
<CardHeader
|
|
54
|
-
<
|
|
49
|
+
<CardHeader pb={2}>
|
|
50
|
+
<HStack spacing={2}>
|
|
55
51
|
<Terminal className="h-4 w-4 text-primary" />
|
|
56
|
-
<
|
|
57
|
-
</
|
|
52
|
+
<Heading size="xs">CLI</Heading>
|
|
53
|
+
</HStack>
|
|
58
54
|
</CardHeader>
|
|
59
|
-
<
|
|
60
|
-
<
|
|
61
|
-
Run <
|
|
62
|
-
</
|
|
63
|
-
</
|
|
55
|
+
<CardBody>
|
|
56
|
+
<Text fontSize="sm" color="gray.500">
|
|
57
|
+
Run <code className="font-mono text-sm">blacksmith dev</code> to start the full stack.
|
|
58
|
+
</Text>
|
|
59
|
+
</CardBody>
|
|
64
60
|
</Card>
|
|
65
61
|
|
|
66
62
|
<Card>
|
|
67
|
-
<CardHeader
|
|
68
|
-
<
|
|
63
|
+
<CardHeader pb={2}>
|
|
64
|
+
<HStack spacing={2}>
|
|
69
65
|
<BookOpen className="h-4 w-4 text-primary" />
|
|
70
|
-
<
|
|
71
|
-
</
|
|
66
|
+
<Heading size="xs">API</Heading>
|
|
67
|
+
</HStack>
|
|
72
68
|
</CardHeader>
|
|
73
|
-
<
|
|
74
|
-
<
|
|
69
|
+
<CardBody>
|
|
70
|
+
<Text fontSize="sm" color="gray.500">
|
|
75
71
|
<a href={`${API_URL}/api/docs/`} target="_blank" rel="noopener noreferrer" className="underline underline-offset-4 hover:text-foreground">
|
|
76
72
|
Swagger UI
|
|
77
73
|
</a>
|
|
@@ -79,24 +75,24 @@ export default function HomePage() {
|
|
|
79
75
|
<a href={`${API_URL}/admin/`} target="_blank" rel="noopener noreferrer" className="underline underline-offset-4 hover:text-foreground">
|
|
80
76
|
Django Admin
|
|
81
77
|
</a>
|
|
82
|
-
</
|
|
83
|
-
</
|
|
78
|
+
</Text>
|
|
79
|
+
</CardBody>
|
|
84
80
|
</Card>
|
|
85
81
|
|
|
86
82
|
<Card>
|
|
87
|
-
<CardHeader
|
|
88
|
-
<
|
|
83
|
+
<CardHeader pb={2}>
|
|
84
|
+
<HStack spacing={2}>
|
|
89
85
|
<Layout className="h-4 w-4 text-primary" />
|
|
90
|
-
<
|
|
91
|
-
</
|
|
86
|
+
<Heading size="xs">Frontend</Heading>
|
|
87
|
+
</HStack>
|
|
92
88
|
</CardHeader>
|
|
93
|
-
<
|
|
94
|
-
<
|
|
89
|
+
<CardBody>
|
|
90
|
+
<Text fontSize="sm" color="gray.500">
|
|
95
91
|
React, TanStack Query, and Tailwind CSS with typed API hooks.
|
|
96
|
-
</
|
|
97
|
-
</
|
|
92
|
+
</Text>
|
|
93
|
+
</CardBody>
|
|
98
94
|
</Card>
|
|
99
|
-
</
|
|
100
|
-
</
|
|
95
|
+
</SimpleGrid>
|
|
96
|
+
</VStack>
|
|
101
97
|
)
|
|
102
98
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Navigate, useLocation } from 'react-router-dom'
|
|
2
|
-
import { Spinner, Alert, AlertTitle, AlertDescription, Button } from '@
|
|
2
|
+
import { Spinner, Alert, AlertIcon, AlertTitle, AlertDescription, Button, Box } from '@chakra-ui/react'
|
|
3
3
|
import { ShieldAlert } from 'lucide-react'
|
|
4
4
|
import { useAuth } from '@/features/auth/hooks/use-auth'
|
|
5
5
|
import { Path } from '@/router/paths'
|
|
@@ -16,7 +16,7 @@ export function AuthGuard({ roles, children }: AuthGuardProps) {
|
|
|
16
16
|
if (isLoading) {
|
|
17
17
|
return (
|
|
18
18
|
<div className="flex items-center justify-center min-h-[60vh]">
|
|
19
|
-
<Spinner
|
|
19
|
+
<Spinner size="lg" />
|
|
20
20
|
</div>
|
|
21
21
|
)
|
|
22
22
|
}
|
|
@@ -38,11 +38,14 @@ export function AuthGuard({ roles, children }: AuthGuardProps) {
|
|
|
38
38
|
<ShieldAlert className="h-8 w-8 text-destructive" />
|
|
39
39
|
</div>
|
|
40
40
|
</div>
|
|
41
|
-
<Alert
|
|
42
|
-
<
|
|
43
|
-
<
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
<Alert status="error" borderRadius="md">
|
|
42
|
+
<AlertIcon />
|
|
43
|
+
<Box>
|
|
44
|
+
<AlertTitle>Access Denied</AlertTitle>
|
|
45
|
+
<AlertDescription>
|
|
46
|
+
You don't have permission to view this page.
|
|
47
|
+
</AlertDescription>
|
|
48
|
+
</Box>
|
|
46
49
|
</Alert>
|
|
47
50
|
<Button variant="outline" onClick={() => window.history.back()}>
|
|
48
51
|
Go Back
|
|
@@ -2,11 +2,13 @@ import { useRouteError, isRouteErrorResponse, useNavigate } from 'react-router-d
|
|
|
2
2
|
import {
|
|
3
3
|
Button,
|
|
4
4
|
Alert,
|
|
5
|
+
AlertIcon,
|
|
5
6
|
AlertTitle,
|
|
6
7
|
AlertDescription,
|
|
7
8
|
Card,
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
CardBody,
|
|
10
|
+
Box,
|
|
11
|
+
} from '@chakra-ui/react'
|
|
10
12
|
import { ArrowLeft, RefreshCw, AlertTriangle } from 'lucide-react'
|
|
11
13
|
|
|
12
14
|
export function RouteErrorBoundary() {
|
|
@@ -38,15 +40,17 @@ export function RouteErrorBoundary() {
|
|
|
38
40
|
return (
|
|
39
41
|
<div className="flex items-center justify-center min-h-screen bg-background px-4">
|
|
40
42
|
<Card className="w-full max-w-md">
|
|
41
|
-
<
|
|
42
|
-
<Alert
|
|
43
|
-
<
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
43
|
+
<CardBody className="pt-6 space-y-4">
|
|
44
|
+
<Alert status="error" borderRadius="md">
|
|
45
|
+
<AlertIcon />
|
|
46
|
+
<Box>
|
|
47
|
+
<AlertTitle>Something went wrong</AlertTitle>
|
|
48
|
+
<AlertDescription>
|
|
49
|
+
{error instanceof Error
|
|
50
|
+
? error.message
|
|
51
|
+
: 'An unexpected error occurred.'}
|
|
52
|
+
</AlertDescription>
|
|
53
|
+
</Box>
|
|
50
54
|
</Alert>
|
|
51
55
|
<div className="flex justify-center">
|
|
52
56
|
<Button onClick={() => window.location.reload()}>
|
|
@@ -54,7 +58,7 @@ export function RouteErrorBoundary() {
|
|
|
54
58
|
Reload Page
|
|
55
59
|
</Button>
|
|
56
60
|
</div>
|
|
57
|
-
</
|
|
61
|
+
</CardBody>
|
|
58
62
|
</Card>
|
|
59
63
|
</div>
|
|
60
64
|
)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Outlet, Link } from 'react-router-dom'
|
|
2
|
-
import {
|
|
2
|
+
import { Divider, Button } from '@chakra-ui/react'
|
|
3
3
|
import { Anvil } from 'lucide-react'
|
|
4
4
|
import { Path } from '@/router/paths'
|
|
5
5
|
|
|
@@ -35,8 +35,8 @@ export function AuthLayoutWrapper() {
|
|
|
35
35
|
<span className="font-semibold">{{projectName}}</span>
|
|
36
36
|
</div>
|
|
37
37
|
<div className="lg:ml-auto">
|
|
38
|
-
<Button variant="ghost" size="sm"
|
|
39
|
-
|
|
38
|
+
<Button variant="ghost" size="sm" as={Link} to={Path.Home}>
|
|
39
|
+
Back to Home
|
|
40
40
|
</Button>
|
|
41
41
|
</div>
|
|
42
42
|
</div>
|
|
@@ -47,19 +47,19 @@ export function AuthLayoutWrapper() {
|
|
|
47
47
|
</div>
|
|
48
48
|
</div>
|
|
49
49
|
|
|
50
|
-
<
|
|
50
|
+
<Divider />
|
|
51
51
|
|
|
52
52
|
<div className="flex items-center justify-center gap-4 p-6">
|
|
53
|
-
<Button variant="link" size="sm" className="text-muted-foreground"
|
|
54
|
-
|
|
53
|
+
<Button variant="link" size="sm" className="text-muted-foreground" as={Link} to="/terms">
|
|
54
|
+
Terms
|
|
55
55
|
</Button>
|
|
56
|
-
<
|
|
57
|
-
<Button variant="link" size="sm" className="text-muted-foreground"
|
|
58
|
-
|
|
56
|
+
<Divider orientation="vertical" h={4} />
|
|
57
|
+
<Button variant="link" size="sm" className="text-muted-foreground" as={Link} to="/privacy">
|
|
58
|
+
Privacy
|
|
59
59
|
</Button>
|
|
60
|
-
<
|
|
61
|
-
<Button variant="link" size="sm" className="text-muted-foreground"
|
|
62
|
-
|
|
60
|
+
<Divider orientation="vertical" h={4} />
|
|
61
|
+
<Button variant="link" size="sm" className="text-muted-foreground" as={Link} to="/support">
|
|
62
|
+
Support
|
|
63
63
|
</Button>
|
|
64
64
|
</div>
|
|
65
65
|
</div>
|