blacksmith-cli 0.1.6 → 0.1.8

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.
Files changed (35) hide show
  1. package/dist/index.js +1745 -689
  2. package/dist/index.js.map +1 -1
  3. package/package.json +2 -1
  4. package/src/templates/frontend/package.json.hbs +4 -4
  5. package/src/templates/frontend/src/__tests__/test-utils.tsx.hbs +5 -4
  6. package/src/templates/frontend/src/app.tsx.hbs +13 -9
  7. package/src/templates/frontend/src/features/auth/adapter.ts.hbs +7 -7
  8. package/src/templates/frontend/src/features/auth/components/auth-provider.tsx.hbs +91 -11
  9. package/src/templates/frontend/src/features/auth/hooks/use-auth.ts.hbs +3 -4
  10. package/src/templates/frontend/src/features/auth/pages/forgot-password-page.tsx.hbs +76 -12
  11. package/src/templates/frontend/src/features/auth/pages/login-page.tsx.hbs +84 -11
  12. package/src/templates/frontend/src/features/auth/pages/register-page.tsx.hbs +85 -14
  13. package/src/templates/frontend/src/features/auth/pages/reset-password-page.tsx.hbs +63 -12
  14. package/src/templates/frontend/src/features/auth/types.ts.hbs +32 -0
  15. package/src/templates/frontend/src/pages/dashboard/components/quick-start-card.tsx.hbs +19 -18
  16. package/src/templates/frontend/src/pages/dashboard/components/stack-cards.tsx.hbs +33 -31
  17. package/src/templates/frontend/src/pages/dashboard/components/welcome-header.tsx.hbs +5 -5
  18. package/src/templates/frontend/src/pages/dashboard/dashboard.tsx.hbs +5 -5
  19. package/src/templates/frontend/src/pages/home/home.tsx.hbs +48 -52
  20. package/src/templates/frontend/src/router/auth-guard.tsx.hbs +10 -7
  21. package/src/templates/frontend/src/router/error-boundary.tsx.hbs +16 -12
  22. package/src/templates/frontend/src/router/layouts/auth-layout.tsx.hbs +12 -12
  23. package/src/templates/frontend/src/router/layouts/main-layout.tsx.hbs +62 -55
  24. package/src/templates/frontend/src/shared/components/loading-spinner.tsx.hbs +6 -6
  25. package/src/templates/frontend/src/shared/components/not-found-page.tsx.hbs +1 -1
  26. package/src/templates/frontend/src/shared/hooks/use-debounce.ts.hbs +18 -2
  27. package/src/templates/frontend/src/styles/globals.css.hbs +3 -1
  28. package/src/templates/frontend/src/theme.ts.hbs +10 -0
  29. package/src/templates/frontend/tailwind.config.js.hbs +2 -4
  30. package/src/templates/resource/frontend/components/{{kebab}}-form.tsx.hbs +3 -2
  31. package/src/templates/resource/frontend/pages/{{kebabs}}-page.tsx.hbs +3 -2
  32. package/src/templates/resource/frontend/pages/{{kebab}}-detail-page.tsx.hbs +5 -3
  33. package/src/templates/resource/pages/components/{{kebab}}-form.tsx.hbs +3 -2
  34. package/src/templates/resource/pages/{{kebabs}}-page.tsx.hbs +3 -2
  35. package/src/templates/resource/pages/{{kebab}}-detail-page.tsx.hbs +5 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blacksmith-cli",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Fullstack Django + React framework — one command, one codebase, one mental model",
5
5
  "type": "module",
6
6
  "bin": {
@@ -34,6 +34,7 @@
34
34
  "author": "",
35
35
  "license": "MIT",
36
36
  "dependencies": {
37
+ "@blacksmith/studio": "file:packages/studio",
37
38
  "chalk": "^5.4.1",
38
39
  "change-case": "^5.4.4",
39
40
  "chokidar": "^5.0.0",
@@ -14,15 +14,15 @@
14
14
  "openapi-ts": "openapi-ts"
15
15
  },
16
16
  "dependencies": {
17
- "@blacksmith-ui/auth": "^0.1.0",
18
- "@blacksmith-ui/forms": "^0.1.0",
19
- "@blacksmith-ui/hooks": "^0.1.0",
20
- "@blacksmith-ui/react": "^0.1.2",
17
+ "@chakra-ui/react": "^2.8.0",
18
+ "@emotion/react": "^11.11.0",
19
+ "@emotion/styled": "^11.11.0",
21
20
  "@hey-api/client-fetch": "^0.9.0",
22
21
  "@hookform/resolvers": "^5.0.0",
23
22
  "@tanstack/react-query": "^5.90.0",
24
23
  "@tanstack/react-query-devtools": "^5.90.0",
25
24
  "clsx": "^2.1.1",
25
+ "framer-motion": "^11.0.0",
26
26
  "lucide-react": "^0.400.0",
27
27
  "react": "^19.1.0",
28
28
  "react-dom": "^19.1.0",
@@ -2,7 +2,8 @@ import { type ReactElement, type ReactNode } from 'react'
2
2
  import { render, type RenderOptions } from '@testing-library/react'
3
3
  import userEvent from '@testing-library/user-event'
4
4
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
5
- import { ThemeProvider } from '@blacksmith-ui/react'
5
+ import { ChakraProvider } from '@chakra-ui/react'
6
+ import theme from '@/theme'
6
7
  import { MemoryRouter } from 'react-router-dom'
7
8
 
8
9
  /**
@@ -36,7 +37,7 @@ interface CustomRenderOptions extends Omit<RenderOptions, 'wrapper'> {
36
37
 
37
38
  /**
38
39
  * Custom render that wraps components with all app providers:
39
- * - ThemeProvider (light mode for consistent snapshots)
40
+ * - ChakraProvider (with theme for consistent snapshots)
40
41
  * - QueryClientProvider (with test-friendly defaults)
41
42
  * - MemoryRouter (for components that use routing hooks)
42
43
  *
@@ -56,13 +57,13 @@ export function renderWithProviders(
56
57
  ) {
57
58
  function Wrapper({ children }: WrapperProps) {
58
59
  return (
59
- <ThemeProvider defaultMode="light" storageKey="test-theme">
60
+ <ChakraProvider theme={theme}>
60
61
  <QueryClientProvider client={queryClient}>
61
62
  <MemoryRouter initialEntries={routerEntries}>
62
63
  {children}
63
64
  </MemoryRouter>
64
65
  </QueryClientProvider>
65
- </ThemeProvider>
66
+ </ChakraProvider>
66
67
  )
67
68
  }
68
69
 
@@ -8,7 +8,8 @@
8
8
  import { QueryClientProvider } from '@tanstack/react-query'
9
9
  import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
10
10
  import { RouterProvider } from 'react-router-dom'
11
- import { ThemeProvider } from '@blacksmith-ui/react'
11
+ import { ChakraProvider, ColorModeScript } from '@chakra-ui/react'
12
+ import theme from '@/theme'
12
13
  import { queryClient } from '@/api/query-client'
13
14
  import { AuthProvider } from '@/features/auth/components/auth-provider'
14
15
  import { router } from '@/router'
@@ -18,13 +19,16 @@ import '@/api/client'
18
19
 
19
20
  export function App() {
20
21
  return (
21
- <ThemeProvider defaultMode="system" storageKey="{{projectName}}-theme">
22
- <QueryClientProvider client={queryClient}>
23
- <AuthProvider>
24
- <RouterProvider router={router} />
25
- </AuthProvider>
26
- {import.meta.env.DEV && <ReactQueryDevtools initialIsOpen={false} />}
27
- </QueryClientProvider>
28
- </ThemeProvider>
22
+ <>
23
+ <ColorModeScript initialColorMode={theme.config.initialColorMode} />
24
+ <ChakraProvider theme={theme}>
25
+ <QueryClientProvider client={queryClient}>
26
+ <AuthProvider>
27
+ <RouterProvider router={router} />
28
+ </AuthProvider>
29
+ {import.meta.env.DEV && <ReactQueryDevtools initialIsOpen={false} />}
30
+ </QueryClientProvider>
31
+ </ChakraProvider>
32
+ </>
29
33
  )
30
34
  }
@@ -1,13 +1,13 @@
1
1
  /**
2
- * Blacksmith Auth Adapter
2
+ * Auth Adapter
3
3
  *
4
- * Connects @blacksmith-ui/auth to the Django JWT backend.
4
+ * Connects the auth system to the Django JWT backend.
5
5
  * Implements the AuthAdapter interface using the generated API client.
6
6
  *
7
7
  * Generated by Blacksmith. You own this file — customize as needed.
8
8
  */
9
9
 
10
- import type { AuthAdapter, AuthUser, AuthResult, AuthError, SocialProvider } from '@blacksmith-ui/auth'
10
+ import type { AuthAdapter, AuthUser, AuthError, SocialProvider } from './types'
11
11
  import { setTokens, getAccessToken, clearTokens } from '@/api/client'
12
12
  import { parseApiError } from '@/shared/hooks/api-error'
13
13
 
@@ -73,7 +73,7 @@ async function fetchCurrentUser(): Promise<AuthUser> {
73
73
  return mapDjangoUser(data)
74
74
  }
75
75
 
76
- export function createBlacksmithAuthAdapter(): AuthAdapter {
76
+ export function createAuthAdapter(): AuthAdapter {
77
77
  // Try to restore session on init
78
78
  ;(async () => {
79
79
  try {
@@ -88,7 +88,7 @@ export function createBlacksmithAuthAdapter(): AuthAdapter {
88
88
  })()
89
89
 
90
90
  return {
91
- async signInWithEmail(email: string, password: string): Promise<AuthResult> {
91
+ async signInWithEmail(email: string, password: string) {
92
92
  try {
93
93
  const data = await apiFetch('/api/auth/login/', {
94
94
  method: 'POST',
@@ -109,7 +109,7 @@ export function createBlacksmithAuthAdapter(): AuthAdapter {
109
109
  email: string,
110
110
  password: string,
111
111
  displayName?: string
112
- ): Promise<AuthResult> {
112
+ ) {
113
113
  try {
114
114
  const data = await apiFetch('/api/auth/register/', {
115
115
  method: 'POST',
@@ -131,7 +131,7 @@ export function createBlacksmithAuthAdapter(): AuthAdapter {
131
131
  }
132
132
  },
133
133
 
134
- async signInWithSocial(_provider: SocialProvider): Promise<AuthResult> {
134
+ async signInWithSocial(_provider: SocialProvider) {
135
135
  return {
136
136
  success: false,
137
137
  error: {
@@ -1,32 +1,112 @@
1
1
  /**
2
2
  * Auth Provider
3
3
  *
4
- * Wraps the app with @blacksmith-ui/auth's AuthProvider configured
5
- * with the Django JWT adapter.
4
+ * Provides auth state and actions via React context,
5
+ * connected to the Django JWT adapter.
6
6
  *
7
7
  * Generated by Blacksmith. You own this file — customize as needed.
8
8
  */
9
9
 
10
- import { AuthProvider as BlacksmithAuthProvider } from '@blacksmith-ui/auth'
11
- import { createBlacksmithAuthAdapter } from '../adapter'
10
+ import { createContext, useContext, useState, useEffect, useCallback } from 'react'
12
11
  import type { ReactNode } from 'react'
12
+ import { createAuthAdapter } from '../adapter'
13
+ import type { AuthUser, AuthError, AuthResult } from '../types'
13
14
 
14
- const adapter = createBlacksmithAuthAdapter()
15
+ interface AuthContextValue {
16
+ user: AuthUser | null
17
+ loading: boolean
18
+ error: AuthError | null
19
+ signInWithEmail: (email: string, password: string) => Promise<AuthResult>
20
+ signUpWithEmail: (email: string, password: string, displayName?: string) => Promise<AuthResult>
21
+ signOut: () => Promise<void>
22
+ sendPasswordResetEmail: (email: string) => Promise<{ success: boolean; error?: AuthError }>
23
+ confirmPasswordReset: (code: string, newPassword: string) => Promise<{ success: boolean; error?: AuthError }>
24
+ }
25
+
26
+ const AuthContext = createContext<AuthContextValue | null>(null)
27
+
28
+ const adapter = createAuthAdapter()
15
29
 
16
30
  interface Props {
17
31
  children: ReactNode
18
32
  }
19
33
 
20
34
  export function AuthProvider({ children }: Props) {
35
+ const [user, setUser] = useState<AuthUser | null>(adapter.getCurrentUser())
36
+ const [loading, setLoading] = useState(true)
37
+ const [error, setError] = useState<AuthError | null>(null)
38
+
39
+ useEffect(() => {
40
+ const unsubscribe = adapter.onAuthStateChanged((u) => {
41
+ setUser(u)
42
+ setLoading(false)
43
+ })
44
+ return unsubscribe
45
+ }, [])
46
+
47
+ const signInWithEmail = useCallback(async (email: string, password: string) => {
48
+ setError(null)
49
+ const result = await adapter.signInWithEmail(email, password)
50
+ if (!result.success && result.error) {
51
+ setError(result.error)
52
+ }
53
+ return result
54
+ }, [])
55
+
56
+ const signUpWithEmail = useCallback(async (email: string, password: string, displayName?: string) => {
57
+ setError(null)
58
+ const result = await adapter.signUpWithEmail(email, password, displayName)
59
+ if (!result.success && result.error) {
60
+ setError(result.error)
61
+ }
62
+ return result
63
+ }, [])
64
+
65
+ const signOut = useCallback(async () => {
66
+ setError(null)
67
+ await adapter.signOut()
68
+ }, [])
69
+
70
+ const sendPasswordResetEmail = useCallback(async (email: string) => {
71
+ setError(null)
72
+ const result = await adapter.sendPasswordResetEmail(email)
73
+ if (!result.success && result.error) {
74
+ setError(result.error)
75
+ }
76
+ return result
77
+ }, [])
78
+
79
+ const confirmPasswordReset = useCallback(async (code: string, newPassword: string) => {
80
+ setError(null)
81
+ const result = await adapter.confirmPasswordReset(code, newPassword)
82
+ if (!result.success && result.error) {
83
+ setError(result.error)
84
+ }
85
+ return result
86
+ }, [])
87
+
21
88
  return (
22
- <BlacksmithAuthProvider
23
- config=\{{
24
- adapter,
25
- // Enable social providers by adding them here:
26
- // socialProviders: ['google', 'github'],
89
+ <AuthContext.Provider
90
+ value=\{{
91
+ user,
92
+ loading,
93
+ error,
94
+ signInWithEmail,
95
+ signUpWithEmail,
96
+ signOut,
97
+ sendPasswordResetEmail,
98
+ confirmPasswordReset,
27
99
  }}
28
100
  >
29
101
  {children}
30
- </BlacksmithAuthProvider>
102
+ </AuthContext.Provider>
31
103
  )
32
104
  }
105
+
106
+ export function useAuthContext() {
107
+ const context = useContext(AuthContext)
108
+ if (!context) {
109
+ throw new Error('useAuthContext must be used within an AuthProvider')
110
+ }
111
+ return context
112
+ }
@@ -1,16 +1,16 @@
1
1
  /**
2
2
  * Auth Hook
3
3
  *
4
- * Re-exports useAuth from @blacksmith-ui/auth for convenience.
4
+ * Provides a convenient interface to the auth context.
5
5
  * Import from here so your app has a single auth import path.
6
6
  *
7
7
  * Generated by Blacksmith. You own this file — customize as needed.
8
8
  */
9
9
 
10
- import { useAuth as useBlacksmithAuth } from '@blacksmith-ui/auth'
10
+ import { useAuthContext } from '../components/auth-provider'
11
11
 
12
12
  export function useAuth() {
13
- const auth = useBlacksmithAuth()
13
+ const auth = useAuthContext()
14
14
 
15
15
  return {
16
16
  user: auth.user,
@@ -22,6 +22,5 @@ export function useAuth() {
22
22
  logout: auth.signOut,
23
23
  sendPasswordResetEmail: auth.sendPasswordResetEmail,
24
24
  confirmPasswordReset: auth.confirmPasswordReset,
25
- socialProviders: auth.socialProviders,
26
25
  }
27
26
  }
@@ -1,37 +1,101 @@
1
1
  /**
2
2
  * Forgot Password Page
3
3
  *
4
- * Uses @blacksmith-ui/auth ForgotPasswordForm connected to Django backend.
4
+ * Password reset request form built with Chakra UI.
5
5
  * Generated by Blacksmith. You own this file — customize as needed.
6
6
  */
7
7
 
8
- import { ForgotPasswordForm } from '@blacksmith-ui/auth'
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 } 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 ForgotPasswordPage() {
15
28
  const navigate = useNavigate()
16
29
  const { sendPasswordResetEmail, error } = useAuth()
30
+
31
+ const [email, setEmail] = useState('')
17
32
  const [loading, setLoading] = useState(false)
33
+ const [sent, setSent] = useState(false)
18
34
 
19
- const handleSubmit = async (data: { email: string }) => {
35
+ const handleSubmit = async (e: React.FormEvent) => {
36
+ e.preventDefault()
20
37
  setLoading(true)
21
38
  try {
22
- await sendPasswordResetEmail(data.email)
23
- // Show success regardless (prevents email enumeration)
39
+ await sendPasswordResetEmail(email)
40
+ setSent(true)
24
41
  } finally {
25
42
  setLoading(false)
26
43
  }
27
44
  }
28
45
 
29
46
  return (
30
- <ForgotPasswordForm
31
- onSubmit={handleSubmit}
32
- onLoginClick={() => navigate(Path.Login)}
33
- error={error}
34
- loading={loading}
35
- />
47
+ <Box>
48
+ <VStack spacing={2} mb={6} align="start">
49
+ <Heading size="lg">Forgot Password</Heading>
50
+ <Text color="gray.500">
51
+ Enter your email and we'll send you a reset link.
52
+ </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
+ {sent && (
63
+ <Alert status="success" borderRadius="md" mb={4}>
64
+ <AlertIcon />
65
+ <AlertDescription>
66
+ If an account with that email exists, a reset link has been sent.
67
+ </AlertDescription>
68
+ </Alert>
69
+ )}
70
+
71
+ <form onSubmit={handleSubmit}>
72
+ <VStack spacing={4}>
73
+ <FormControl isRequired>
74
+ <FormLabel>Email</FormLabel>
75
+ <Input
76
+ type="email"
77
+ value={email}
78
+ onChange={(e) => setEmail(e.target.value)}
79
+ placeholder="you@example.com"
80
+ />
81
+ </FormControl>
82
+
83
+ <Button
84
+ type="submit"
85
+ colorScheme="blue"
86
+ width="full"
87
+ isLoading={loading}
88
+ >
89
+ Send Reset Link
90
+ </Button>
91
+ </VStack>
92
+ </form>
93
+
94
+ <Text fontSize="sm" color="gray.500" mt={6} textAlign="center">
95
+ <ChakraLink color="blue.500" onClick={() => navigate(Path.Login)}>
96
+ Back to Sign In
97
+ </ChakraLink>
98
+ </Text>
99
+ </Box>
36
100
  )
37
101
  }
@@ -1,11 +1,25 @@
1
1
  /**
2
2
  * Login Page
3
3
  *
4
- * Uses @blacksmith-ui/auth LoginForm connected to Django JWT backend.
4
+ * Login form built with Chakra UI, connected to Django JWT backend.
5
5
  * Generated by Blacksmith. You own this file — customize as needed.
6
6
  */
7
7
 
8
- import { LoginForm } from '@blacksmith-ui/auth'
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, useSearchParams } from 'react-router-dom'
10
24
  import { useAuth } from '../hooks/use-auth'
11
25
  import { Path } from '@/router/paths'
@@ -15,22 +29,81 @@ export default function LoginPage() {
15
29
  const [searchParams] = useSearchParams()
16
30
  const { login, error, isLoading } = useAuth()
17
31
 
32
+ const [email, setEmail] = useState('')
33
+ const [password, setPassword] = useState('')
34
+
18
35
  const redirectTo = searchParams.get('redirect') || Path.Home
19
36
 
20
- const handleSubmit = async (data: { email: string; password: string }) => {
21
- const result = await login(data.email, data.password)
37
+ const handleSubmit = async (e: React.FormEvent) => {
38
+ e.preventDefault()
39
+ const result = await login(email, password)
22
40
  if (result.success) {
23
41
  navigate(redirectTo, { replace: true })
24
42
  }
25
43
  }
26
44
 
27
45
  return (
28
- <LoginForm
29
- onSubmit={handleSubmit}
30
- onRegisterClick={() => navigate(Path.Register)}
31
- onForgotPasswordClick={() => navigate(Path.ForgotPassword)}
32
- error={error}
33
- loading={isLoading}
34
- />
46
+ <Box>
47
+ <VStack spacing={2} mb={6} align="start">
48
+ <Heading size="lg">Sign In</Heading>
49
+ <Text color="gray.500">Enter your credentials to access your account.</Text>
50
+ </VStack>
51
+
52
+ {error && (
53
+ <Alert status="error" borderRadius="md" mb={4}>
54
+ <AlertIcon />
55
+ <AlertDescription>{error.message}</AlertDescription>
56
+ </Alert>
57
+ )}
58
+
59
+ <form onSubmit={handleSubmit}>
60
+ <VStack spacing={4}>
61
+ <FormControl isRequired>
62
+ <FormLabel>Email</FormLabel>
63
+ <Input
64
+ type="email"
65
+ value={email}
66
+ onChange={(e) => setEmail(e.target.value)}
67
+ placeholder="you@example.com"
68
+ />
69
+ </FormControl>
70
+
71
+ <FormControl isRequired>
72
+ <FormLabel>Password</FormLabel>
73
+ <Input
74
+ type="password"
75
+ value={password}
76
+ onChange={(e) => setPassword(e.target.value)}
77
+ placeholder="Enter your password"
78
+ />
79
+ </FormControl>
80
+
81
+ <Button
82
+ type="submit"
83
+ colorScheme="blue"
84
+ width="full"
85
+ isLoading={isLoading}
86
+ >
87
+ Sign In
88
+ </Button>
89
+ </VStack>
90
+ </form>
91
+
92
+ <VStack spacing={2} mt={6}>
93
+ <ChakraLink
94
+ color="blue.500"
95
+ fontSize="sm"
96
+ onClick={() => navigate(Path.ForgotPassword)}
97
+ >
98
+ Forgot your password?
99
+ </ChakraLink>
100
+ <Text fontSize="sm" color="gray.500">
101
+ Don't have an account?{' '}
102
+ <ChakraLink color="blue.500" onClick={() => navigate(Path.Register)}>
103
+ Sign Up
104
+ </ChakraLink>
105
+ </Text>
106
+ </VStack>
107
+ </Box>
35
108
  )
36
109
  }
@@ -1,11 +1,25 @@
1
1
  /**
2
2
  * Register Page
3
3
  *
4
- * Uses @blacksmith-ui/auth RegisterForm connected to Django JWT backend.
4
+ * Registration form built with Chakra UI, connected to Django JWT backend.
5
5
  * Generated by Blacksmith. You own this file — customize as needed.
6
6
  */
7
7
 
8
- import { RegisterForm } from '@blacksmith-ui/auth'
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 } from 'react-router-dom'
10
24
  import { useAuth } from '../hooks/use-auth'
11
25
  import { Path } from '@/router/paths'
@@ -14,23 +28,80 @@ export default function RegisterPage() {
14
28
  const navigate = useNavigate()
15
29
  const { register, error, isLoading } = useAuth()
16
30
 
17
- const handleSubmit = async (data: {
18
- email: string
19
- password: string
20
- displayName: string
21
- }) => {
22
- const result = await register(data.email, data.password, data.displayName)
31
+ const [displayName, setDisplayName] = useState('')
32
+ const [email, setEmail] = useState('')
33
+ const [password, setPassword] = useState('')
34
+
35
+ const handleSubmit = async (e: React.FormEvent) => {
36
+ e.preventDefault()
37
+ const result = await register(email, password, displayName)
23
38
  if (result.success) {
24
39
  navigate(Path.Home, { replace: true })
25
40
  }
26
41
  }
27
42
 
28
43
  return (
29
- <RegisterForm
30
- onSubmit={handleSubmit}
31
- onLoginClick={() => navigate(Path.Login)}
32
- error={error}
33
- loading={isLoading}
34
- />
44
+ <Box>
45
+ <VStack spacing={2} mb={6} align="start">
46
+ <Heading size="lg">Create Account</Heading>
47
+ <Text color="gray.500">Sign up to get started.</Text>
48
+ </VStack>
49
+
50
+ {error && (
51
+ <Alert status="error" borderRadius="md" mb={4}>
52
+ <AlertIcon />
53
+ <AlertDescription>{error.message}</AlertDescription>
54
+ </Alert>
55
+ )}
56
+
57
+ <form onSubmit={handleSubmit}>
58
+ <VStack spacing={4}>
59
+ <FormControl>
60
+ <FormLabel>Name</FormLabel>
61
+ <Input
62
+ value={displayName}
63
+ onChange={(e) => setDisplayName(e.target.value)}
64
+ placeholder="Your name"
65
+ />
66
+ </FormControl>
67
+
68
+ <FormControl isRequired>
69
+ <FormLabel>Email</FormLabel>
70
+ <Input
71
+ type="email"
72
+ value={email}
73
+ onChange={(e) => setEmail(e.target.value)}
74
+ placeholder="you@example.com"
75
+ />
76
+ </FormControl>
77
+
78
+ <FormControl isRequired>
79
+ <FormLabel>Password</FormLabel>
80
+ <Input
81
+ type="password"
82
+ value={password}
83
+ onChange={(e) => setPassword(e.target.value)}
84
+ placeholder="Choose a password"
85
+ />
86
+ </FormControl>
87
+
88
+ <Button
89
+ type="submit"
90
+ colorScheme="blue"
91
+ width="full"
92
+ isLoading={isLoading}
93
+ >
94
+ Create Account
95
+ </Button>
96
+ </VStack>
97
+ </form>
98
+
99
+ <Text fontSize="sm" color="gray.500" mt={6} textAlign="center">
100
+ Already have an account?{' '}
101
+ <ChakraLink color="blue.500" onClick={() => navigate(Path.Login)}>
102
+ Sign In
103
+ </ChakraLink>
104
+ </Text>
105
+ </Box>
35
106
  )
36
107
  }