blacksmith-cli 0.1.1

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 (103) hide show
  1. package/README.md +210 -0
  2. package/bin/blacksmith.js +20 -0
  3. package/dist/index.js +4404 -0
  4. package/dist/index.js.map +1 -0
  5. package/package.json +51 -0
  6. package/src/templates/backend/.env.example.hbs +10 -0
  7. package/src/templates/backend/apps/__init__.py.hbs +0 -0
  8. package/src/templates/backend/apps/users/__init__.py.hbs +0 -0
  9. package/src/templates/backend/apps/users/admin.py.hbs +26 -0
  10. package/src/templates/backend/apps/users/managers.py.hbs +25 -0
  11. package/src/templates/backend/apps/users/models.py.hbs +25 -0
  12. package/src/templates/backend/apps/users/serializers.py.hbs +94 -0
  13. package/src/templates/backend/apps/users/tests.py.hbs +47 -0
  14. package/src/templates/backend/apps/users/urls.py.hbs +10 -0
  15. package/src/templates/backend/apps/users/views.py.hbs +175 -0
  16. package/src/templates/backend/config/__init__.py.hbs +0 -0
  17. package/src/templates/backend/config/asgi.py.hbs +9 -0
  18. package/src/templates/backend/config/settings/__init__.py.hbs +13 -0
  19. package/src/templates/backend/config/settings/base.py.hbs +117 -0
  20. package/src/templates/backend/config/settings/development.py.hbs +19 -0
  21. package/src/templates/backend/config/settings/production.py.hbs +31 -0
  22. package/src/templates/backend/config/urls.py.hbs +26 -0
  23. package/src/templates/backend/config/wsgi.py.hbs +9 -0
  24. package/src/templates/backend/manage.py.hbs +22 -0
  25. package/src/templates/backend/requirements.txt.hbs +7 -0
  26. package/src/templates/frontend/.env.hbs +1 -0
  27. package/src/templates/frontend/index.html.hbs +13 -0
  28. package/src/templates/frontend/openapi-ts.config.ts.hbs +29 -0
  29. package/src/templates/frontend/package.json.hbs +44 -0
  30. package/src/templates/frontend/postcss.config.js.hbs +6 -0
  31. package/src/templates/frontend/src/api/client.ts.hbs +110 -0
  32. package/src/templates/frontend/src/api/generated/.gitkeep +0 -0
  33. package/src/templates/frontend/src/api/generated/client.gen.ts +13 -0
  34. package/src/templates/frontend/src/api/query-client.ts.hbs +22 -0
  35. package/src/templates/frontend/src/app.tsx.hbs +30 -0
  36. package/src/templates/frontend/src/features/auth/adapter.ts.hbs +198 -0
  37. package/src/templates/frontend/src/features/auth/components/auth-provider.tsx.hbs +32 -0
  38. package/src/templates/frontend/src/features/auth/hooks/use-auth.ts.hbs +27 -0
  39. package/src/templates/frontend/src/features/auth/index.ts.hbs +3 -0
  40. package/src/templates/frontend/src/features/auth/pages/forgot-password-page.tsx.hbs +37 -0
  41. package/src/templates/frontend/src/features/auth/pages/login-page.tsx.hbs +36 -0
  42. package/src/templates/frontend/src/features/auth/pages/register-page.tsx.hbs +36 -0
  43. package/src/templates/frontend/src/features/auth/pages/reset-password-page.tsx.hbs +41 -0
  44. package/src/templates/frontend/src/features/auth/routes.tsx.hbs +13 -0
  45. package/src/templates/frontend/src/main.tsx.hbs +10 -0
  46. package/src/templates/frontend/src/pages/dashboard/components/quick-start-card.tsx.hbs +36 -0
  47. package/src/templates/frontend/src/pages/dashboard/components/stack-cards.tsx.hbs +69 -0
  48. package/src/templates/frontend/src/pages/dashboard/components/welcome-header.tsx.hbs +14 -0
  49. package/src/templates/frontend/src/pages/dashboard/dashboard.tsx.hbs +21 -0
  50. package/src/templates/frontend/src/pages/dashboard/index.ts.hbs +1 -0
  51. package/src/templates/frontend/src/pages/dashboard/routes.tsx.hbs +7 -0
  52. package/src/templates/frontend/src/pages/home/components/features-grid.tsx.hbs +88 -0
  53. package/src/templates/frontend/src/pages/home/components/getting-started.tsx.hbs +88 -0
  54. package/src/templates/frontend/src/pages/home/components/hero-section.tsx.hbs +47 -0
  55. package/src/templates/frontend/src/pages/home/components/resources-section.tsx.hbs +34 -0
  56. package/src/templates/frontend/src/pages/home/home.tsx.hbs +20 -0
  57. package/src/templates/frontend/src/pages/home/index.ts.hbs +1 -0
  58. package/src/templates/frontend/src/pages/home/routes.tsx.hbs +7 -0
  59. package/src/templates/frontend/src/router/auth-guard.tsx.hbs +57 -0
  60. package/src/templates/frontend/src/router/error-boundary.tsx.hbs +61 -0
  61. package/src/templates/frontend/src/router/index.tsx.hbs +12 -0
  62. package/src/templates/frontend/src/router/layouts/auth-layout.tsx.hbs +68 -0
  63. package/src/templates/frontend/src/router/layouts/main-layout.tsx.hbs +137 -0
  64. package/src/templates/frontend/src/router/paths.ts.hbs +38 -0
  65. package/src/templates/frontend/src/router/routes.tsx.hbs +64 -0
  66. package/src/templates/frontend/src/shared/components/loading-spinner.tsx.hbs +20 -0
  67. package/src/templates/frontend/src/shared/components/not-found-page.tsx.hbs +31 -0
  68. package/src/templates/frontend/src/shared/hooks/api-error.ts.hbs +147 -0
  69. package/src/templates/frontend/src/shared/hooks/use-api-mutation.ts.hbs +88 -0
  70. package/src/templates/frontend/src/shared/hooks/use-api-query.ts.hbs +66 -0
  71. package/src/templates/frontend/src/shared/hooks/use-debounce.ts.hbs +10 -0
  72. package/src/templates/frontend/src/styles/globals.css.hbs +62 -0
  73. package/src/templates/frontend/src/vite-env.d.ts.hbs +1 -0
  74. package/src/templates/frontend/tailwind.config.js.hbs +73 -0
  75. package/src/templates/frontend/tsconfig.app.json.hbs +25 -0
  76. package/src/templates/frontend/tsconfig.json.hbs +7 -0
  77. package/src/templates/frontend/tsconfig.node.json.hbs +18 -0
  78. package/src/templates/frontend/vite.config.ts.hbs +21 -0
  79. package/src/templates/resource/backend/__init__.py.hbs +0 -0
  80. package/src/templates/resource/backend/admin.py.hbs +10 -0
  81. package/src/templates/resource/backend/models.py.hbs +24 -0
  82. package/src/templates/resource/backend/serializers.py.hbs +21 -0
  83. package/src/templates/resource/backend/tests.py.hbs +35 -0
  84. package/src/templates/resource/backend/urls.py.hbs +10 -0
  85. package/src/templates/resource/backend/views.py.hbs +32 -0
  86. package/src/templates/resource/frontend/components/{{kebab}}-card.tsx.hbs +39 -0
  87. package/src/templates/resource/frontend/components/{{kebab}}-form.tsx.hbs +106 -0
  88. package/src/templates/resource/frontend/components/{{kebab}}-list.tsx.hbs +49 -0
  89. package/src/templates/resource/frontend/hooks/use-{{kebabs}}-query.ts.hbs +35 -0
  90. package/src/templates/resource/frontend/hooks/use-{{kebab}}-mutations.ts.hbs +39 -0
  91. package/src/templates/resource/frontend/index.ts.hbs +6 -0
  92. package/src/templates/resource/frontend/pages/{{kebabs}}-page.tsx.hbs +33 -0
  93. package/src/templates/resource/frontend/pages/{{kebab}}-detail-page.tsx.hbs +96 -0
  94. package/src/templates/resource/frontend/routes.tsx.hbs +15 -0
  95. package/src/templates/resource/pages/components/{{kebab}}-card.tsx.hbs +39 -0
  96. package/src/templates/resource/pages/components/{{kebab}}-form.tsx.hbs +106 -0
  97. package/src/templates/resource/pages/components/{{kebab}}-list.tsx.hbs +49 -0
  98. package/src/templates/resource/pages/hooks/use-{{kebabs}}-query.ts.hbs +35 -0
  99. package/src/templates/resource/pages/hooks/use-{{kebab}}-mutations.ts.hbs +39 -0
  100. package/src/templates/resource/pages/index.ts.hbs +6 -0
  101. package/src/templates/resource/pages/routes.tsx.hbs +15 -0
  102. package/src/templates/resource/pages/{{kebabs}}-page.tsx.hbs +33 -0
  103. package/src/templates/resource/pages/{{kebab}}-detail-page.tsx.hbs +96 -0
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Register Page
3
+ *
4
+ * Uses @blacksmith-ui/auth RegisterForm connected to Django JWT backend.
5
+ * Generated by Blacksmith. You own this file — customize as needed.
6
+ */
7
+
8
+ import { RegisterForm } from '@blacksmith-ui/auth'
9
+ import { useNavigate } from 'react-router-dom'
10
+ import { useAuth } from '../hooks/use-auth'
11
+ import { Path } from '@/router/paths'
12
+
13
+ export default function RegisterPage() {
14
+ const navigate = useNavigate()
15
+ const { register, error, isLoading } = useAuth()
16
+
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)
23
+ if (result.success) {
24
+ navigate(Path.Home, { replace: true })
25
+ }
26
+ }
27
+
28
+ return (
29
+ <RegisterForm
30
+ onSubmit={handleSubmit}
31
+ onLoginClick={() => navigate(Path.Login)}
32
+ error={error}
33
+ loading={isLoading}
34
+ />
35
+ )
36
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Reset Password Page
3
+ *
4
+ * Uses @blacksmith-ui/auth ResetPasswordForm connected to Django backend.
5
+ * Generated by Blacksmith. You own this file — customize as needed.
6
+ */
7
+
8
+ import { ResetPasswordForm } from '@blacksmith-ui/auth'
9
+ import { useNavigate, useParams } from 'react-router-dom'
10
+ import { useAuth } from '../hooks/use-auth'
11
+ import { useState } from 'react'
12
+ import { Path } from '@/router/paths'
13
+
14
+ export default function ResetPasswordPage() {
15
+ const navigate = useNavigate()
16
+ const { token } = useParams<{ token: string }>()
17
+ const { confirmPasswordReset, error } = useAuth()
18
+ const [loading, setLoading] = useState(false)
19
+
20
+ const handleSubmit = async (data: { password: string; code: string }) => {
21
+ setLoading(true)
22
+ try {
23
+ const result = await confirmPasswordReset(data.code, data.password)
24
+ if (result.success) {
25
+ navigate(Path.Login, { replace: true })
26
+ }
27
+ } finally {
28
+ setLoading(false)
29
+ }
30
+ }
31
+
32
+ return (
33
+ <ResetPasswordForm
34
+ onSubmit={handleSubmit}
35
+ code={token || ''}
36
+ onLoginClick={() => navigate(Path.Login)}
37
+ error={error}
38
+ loading={loading}
39
+ />
40
+ )
41
+ }
@@ -0,0 +1,13 @@
1
+ import type { RouteObject } from 'react-router-dom'
2
+ import { Path } from '@/router/paths'
3
+ import LoginPage from './pages/login-page'
4
+ import RegisterPage from './pages/register-page'
5
+ import ForgotPasswordPage from './pages/forgot-password-page'
6
+ import ResetPasswordPage from './pages/reset-password-page'
7
+
8
+ export const authRoutes: RouteObject[] = [
9
+ { path: Path.Login, element: <LoginPage /> },
10
+ { path: Path.Register, element: <RegisterPage /> },
11
+ { path: Path.ForgotPassword, element: <ForgotPasswordPage /> },
12
+ { path: Path.ResetPassword, element: <ResetPasswordPage /> },
13
+ ]
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import { App } from './app'
4
+ import '@/styles/globals.css'
5
+
6
+ createRoot(document.getElementById('root')!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ )
@@ -0,0 +1,36 @@
1
+ import {
2
+ Card,
3
+ CardHeader,
4
+ CardTitle,
5
+ CardDescription,
6
+ CardContent,
7
+ Badge,
8
+ Flex,
9
+ Text,
10
+ } from '@blacksmith-ui/react'
11
+ import { Rocket, Terminal } from 'lucide-react'
12
+
13
+ export function QuickStartCard() {
14
+ return (
15
+ <Card>
16
+ <CardHeader className="pb-3">
17
+ <Flex align="center" justify="between">
18
+ <CardTitle className="text-base">Quick Start</CardTitle>
19
+ <Badge variant="outline">
20
+ <Rocket className="mr-1 h-3 w-3" />
21
+ Guide
22
+ </Badge>
23
+ </Flex>
24
+ </CardHeader>
25
+ <CardContent>
26
+ <CardDescription>
27
+ Scaffold a new resource with models, API, and pages in one command.
28
+ </CardDescription>
29
+ <Flex align="center" gap={2} className="mt-4 rounded-lg bg-muted px-3 py-2">
30
+ <Terminal className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
31
+ <Text as="code" size="sm" className="font-mono">blacksmith make:resource Post</Text>
32
+ </Flex>
33
+ </CardContent>
34
+ </Card>
35
+ )
36
+ }
@@ -0,0 +1,69 @@
1
+ import {
2
+ Card,
3
+ CardHeader,
4
+ CardTitle,
5
+ CardDescription,
6
+ CardContent,
7
+ Badge,
8
+ Button,
9
+ Flex,
10
+ } from '@blacksmith-ui/react'
11
+ import { Database, Layout } from 'lucide-react'
12
+ import { Link } from 'react-router-dom'
13
+ import { Path } from '@/router/paths'
14
+
15
+ const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000'
16
+
17
+ export function BackendCard() {
18
+ return (
19
+ <Card>
20
+ <CardHeader className="pb-3">
21
+ <Flex align="center" justify="between">
22
+ <CardTitle className="text-base">Backend</CardTitle>
23
+ <Badge variant="outline">
24
+ <Database className="mr-1 h-3 w-3" />
25
+ Django
26
+ </Badge>
27
+ </Flex>
28
+ </CardHeader>
29
+ <CardContent>
30
+ <CardDescription>
31
+ Django REST API with JWT auth, serializers, and auto-generated OpenAPI docs.
32
+ </CardDescription>
33
+ <div className="mt-4">
34
+ <Button variant="outline" size="sm" asChild>
35
+ <a href={`${API_URL}/api/docs/`} target="_blank" rel="noopener noreferrer">
36
+ View API Docs
37
+ </a>
38
+ </Button>
39
+ </div>
40
+ </CardContent>
41
+ </Card>
42
+ )
43
+ }
44
+
45
+ export function FrontendCard() {
46
+ return (
47
+ <Card>
48
+ <CardHeader className="pb-3">
49
+ <Flex align="center" justify="between">
50
+ <CardTitle className="text-base">Frontend</CardTitle>
51
+ <Badge variant="outline">
52
+ <Layout className="mr-1 h-3 w-3" />
53
+ React
54
+ </Badge>
55
+ </Flex>
56
+ </CardHeader>
57
+ <CardContent>
58
+ <CardDescription>
59
+ React + TypeScript with TanStack Query, React Router, and Tailwind CSS.
60
+ </CardDescription>
61
+ <div className="mt-4">
62
+ <Button variant="outline" size="sm" asChild>
63
+ <Link to={Path.Home}>Home Page</Link>
64
+ </Button>
65
+ </div>
66
+ </CardContent>
67
+ </Card>
68
+ )
69
+ }
@@ -0,0 +1,14 @@
1
+ import { Stack, Typography, Text } from '@blacksmith-ui/react'
2
+
3
+ interface WelcomeHeaderProps {
4
+ displayName: string
5
+ }
6
+
7
+ export function WelcomeHeader({ displayName }: WelcomeHeaderProps) {
8
+ return (
9
+ <Stack gap={1}>
10
+ <Typography variant="h2">Dashboard</Typography>
11
+ <Text color="muted">Welcome back, {displayName}.</Text>
12
+ </Stack>
13
+ )
14
+ }
@@ -0,0 +1,21 @@
1
+ import { Stack, Divider, Grid } from '@blacksmith-ui/react'
2
+ import { useAuth } from '@/features/auth/hooks/use-auth'
3
+ import { WelcomeHeader } from './components/welcome-header'
4
+ import { QuickStartCard } from './components/quick-start-card'
5
+ import { BackendCard, FrontendCard } from './components/stack-cards'
6
+
7
+ export default function DashboardPage() {
8
+ const { user } = useAuth()
9
+
10
+ return (
11
+ <Stack gap={8}>
12
+ <WelcomeHeader displayName={user?.displayName || user?.email || 'there'} />
13
+ <Divider />
14
+ <Grid columns={{ base: 1, sm: 2, lg: 3 }} gap={4}>
15
+ <QuickStartCard />
16
+ <BackendCard />
17
+ <FrontendCard />
18
+ </Grid>
19
+ </Stack>
20
+ )
21
+ }
@@ -0,0 +1 @@
1
+ export { dashboardRoutes } from './routes'
@@ -0,0 +1,7 @@
1
+ import type { RouteObject } from 'react-router-dom'
2
+ import { Path } from '@/router/paths'
3
+ import DashboardPage from './dashboard'
4
+
5
+ export const dashboardRoutes: RouteObject[] = [
6
+ { path: Path.Dashboard, element: <DashboardPage /> },
7
+ ]
@@ -0,0 +1,88 @@
1
+ import {
2
+ Card,
3
+ CardHeader,
4
+ CardTitle,
5
+ CardDescription,
6
+ CardContent,
7
+ Badge,
8
+ Stack,
9
+ Flex,
10
+ Grid,
11
+ Typography,
12
+ Text,
13
+ } from '@blacksmith-ui/react'
14
+ import { Database, Layout, Shield, Code2, Zap, Rocket } from 'lucide-react'
15
+
16
+ const features = [
17
+ {
18
+ icon: Database,
19
+ title: 'Django REST API',
20
+ description: 'Production-ready backend with JWT auth, serializers, and OpenAPI docs.',
21
+ badge: 'Backend',
22
+ },
23
+ {
24
+ icon: Layout,
25
+ title: 'React + TypeScript',
26
+ description: 'Vite-powered frontend with TanStack Query, React Router, and Tailwind CSS.',
27
+ badge: 'Frontend',
28
+ },
29
+ {
30
+ icon: Shield,
31
+ title: 'Authentication',
32
+ description: 'Complete auth flow with login, register, password reset, and role guards.',
33
+ badge: 'Full Stack',
34
+ },
35
+ {
36
+ icon: Code2,
37
+ title: 'Type-Safe API',
38
+ description: 'Auto-generated TypeScript client from your OpenAPI schema via blacksmith sync.',
39
+ badge: 'DX',
40
+ },
41
+ {
42
+ icon: Zap,
43
+ title: 'Blacksmith UI',
44
+ description: '80+ accessible components, form library, hooks, and dark mode theming.',
45
+ badge: 'UI',
46
+ },
47
+ {
48
+ icon: Rocket,
49
+ title: 'Developer Experience',
50
+ description: 'Hot reload, AI-ready CLAUDE.md, and one command to run the full stack.',
51
+ badge: 'DX',
52
+ },
53
+ ]
54
+
55
+ export function FeaturesGrid() {
56
+ return (
57
+ <Stack as="section" gap={6}>
58
+ <Stack gap={2} align="center">
59
+ <Typography variant="h2">What's Included</Typography>
60
+ <Text color="muted">
61
+ A batteries-included stack so you can focus on your product.
62
+ </Text>
63
+ </Stack>
64
+ <Grid columns={{ base: 1, sm: 2, lg: 3 }} gap={4}>
65
+ {features.map((feature) => (
66
+ <Card key={feature.title} className="group transition-colors hover:border-primary/50">
67
+ <CardHeader className="pb-3">
68
+ <Flex align="start" justify="between">
69
+ <Flex align="center" justify="center" className="h-10 w-10 rounded-lg bg-primary/10 transition-colors group-hover:bg-primary/20">
70
+ <feature.icon className="h-5 w-5 text-primary" />
71
+ </Flex>
72
+ <Badge variant="outline" className="text-xs">
73
+ {feature.badge}
74
+ </Badge>
75
+ </Flex>
76
+ </CardHeader>
77
+ <CardContent>
78
+ <Stack gap={1.5}>
79
+ <CardTitle className="text-base">{feature.title}</CardTitle>
80
+ <CardDescription>{feature.description}</CardDescription>
81
+ </Stack>
82
+ </CardContent>
83
+ </Card>
84
+ ))}
85
+ </Grid>
86
+ </Stack>
87
+ )
88
+ }
@@ -0,0 +1,88 @@
1
+ import {
2
+ Card,
3
+ CardHeader,
4
+ CardTitle,
5
+ CardDescription,
6
+ CardContent,
7
+ Button,
8
+ Tooltip,
9
+ Stack,
10
+ Flex,
11
+ Grid,
12
+ Typography,
13
+ Text,
14
+ } from '@blacksmith-ui/react'
15
+ import { Terminal, Copy, Check } from 'lucide-react'
16
+ import { useCopyToClipboard } from '@blacksmith-ui/hooks'
17
+
18
+ const steps = [
19
+ {
20
+ command: 'blacksmith dev',
21
+ title: 'Start the dev server',
22
+ description: 'Runs Django, Vite, and OpenAPI watcher concurrently.',
23
+ },
24
+ {
25
+ command: 'blacksmith make:resource Post',
26
+ title: 'Scaffold a resource',
27
+ description: 'Generates model, serializer, viewset, hooks, and pages.',
28
+ },
29
+ {
30
+ command: 'blacksmith sync',
31
+ title: 'Sync API types',
32
+ description: 'Regenerates the TypeScript client from your Django schema.',
33
+ },
34
+ ]
35
+
36
+ function CommandBlock({ command }: { command: string }) {
37
+ const { status, copy } = useCopyToClipboard(2000)
38
+ const copied = status === 'copied'
39
+
40
+ return (
41
+ <Flex align="center" gap={2} className="rounded-lg bg-muted px-3 py-2">
42
+ <Terminal className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
43
+ <Text as="code" size="sm" className="flex-1 font-mono">{command}</Text>
44
+ <Tooltip content={copied ? 'Copied!' : 'Copy command'}>
45
+ <Button variant="ghost" size="icon" className="h-7 w-7" onClick={() => copy(command)}>
46
+ {copied ? (
47
+ <Check className="h-3.5 w-3.5 text-green-500" />
48
+ ) : (
49
+ <Copy className="h-3.5 w-3.5" />
50
+ )}
51
+ </Button>
52
+ </Tooltip>
53
+ </Flex>
54
+ )
55
+ }
56
+
57
+ export function GettingStarted() {
58
+ return (
59
+ <Stack as="section" gap={6}>
60
+ <Stack gap={2} align="center">
61
+ <Typography variant="h2">Get Started in 3 Steps</Typography>
62
+ <Text color="muted">
63
+ Everything you need to go from scaffold to production.
64
+ </Text>
65
+ </Stack>
66
+ <Grid columns={{ base: 1, md: 3 }} gap={4}>
67
+ {steps.map((step, index) => (
68
+ <Card key={step.command}>
69
+ <CardHeader className="pb-3">
70
+ <Flex align="center" gap={3}>
71
+ <Flex align="center" justify="center" className="h-8 w-8 shrink-0 rounded-full bg-primary text-primary-foreground">
72
+ <Text size="sm" weight="semibold">{index + 1}</Text>
73
+ </Flex>
74
+ <CardTitle className="text-base">{step.title}</CardTitle>
75
+ </Flex>
76
+ </CardHeader>
77
+ <CardContent>
78
+ <Stack gap={3}>
79
+ <CardDescription>{step.description}</CardDescription>
80
+ <CommandBlock command={step.command} />
81
+ </Stack>
82
+ </CardContent>
83
+ </Card>
84
+ ))}
85
+ </Grid>
86
+ </Stack>
87
+ )
88
+ }
@@ -0,0 +1,47 @@
1
+ import { Stack, Flex, Typography, Text, Button } from '@blacksmith-ui/react'
2
+ import { Rocket, BookOpen } from 'lucide-react'
3
+ import { Link } from 'react-router-dom'
4
+ import { Path } from '@/router/paths'
5
+
6
+ const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000'
7
+
8
+ interface HeroSectionProps {
9
+ isAuthenticated: boolean
10
+ }
11
+
12
+ export function HeroSection({ isAuthenticated }: HeroSectionProps) {
13
+ return (
14
+ <Stack as="section" gap={6} align="center" className="pt-10 pb-2">
15
+ <Typography variant="h1" className="sm:text-5xl">
16
+ Welcome to{' '}
17
+ <Text as="span" color="primary">{{projectName}}</Text>
18
+ </Typography>
19
+ <Text size="lg" color="muted" align="center" className="max-w-2xl">
20
+ Your fullstack Django + React application is ready to go.
21
+ </Text>
22
+ <Flex justify="center" gap={3} className="pt-2">
23
+ {isAuthenticated ? (
24
+ <Button size="lg" asChild>
25
+ <Link to={Path.Dashboard}>
26
+ <Rocket className="mr-2 h-4 w-4" />
27
+ Dashboard
28
+ </Link>
29
+ </Button>
30
+ ) : (
31
+ <Button size="lg" asChild>
32
+ <Link to={Path.Login}>
33
+ <Rocket className="mr-2 h-4 w-4" />
34
+ Sign In
35
+ </Link>
36
+ </Button>
37
+ )}
38
+ <Button variant="outline" size="lg" asChild>
39
+ <a href={`${API_URL}/api/docs/`} target="_blank" rel="noopener noreferrer">
40
+ <BookOpen className="mr-2 h-4 w-4" />
41
+ API Docs
42
+ </a>
43
+ </Button>
44
+ </Flex>
45
+ </Stack>
46
+ )
47
+ }
@@ -0,0 +1,34 @@
1
+ import { Stack, Flex, Typography, Text, Button } from '@blacksmith-ui/react'
2
+ import { ExternalLink } from 'lucide-react'
3
+
4
+ const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000'
5
+
6
+ const links = [
7
+ { label: 'Swagger UI', href: `${API_URL}/api/docs/` },
8
+ { label: 'ReDoc', href: `${API_URL}/api/redoc/` },
9
+ { label: 'OpenAPI Schema', href: `${API_URL}/api/schema/` },
10
+ { label: 'Django Admin', href: `${API_URL}/admin/` },
11
+ ]
12
+
13
+ export function ResourcesSection() {
14
+ return (
15
+ <Stack as="section" gap={4}>
16
+ <Stack gap={2} align="center">
17
+ <Typography variant="h2">Resources</Typography>
18
+ <Text color="muted">
19
+ Documentation and tools to help you build.
20
+ </Text>
21
+ </Stack>
22
+ <Flex justify="center" wrap="wrap" gap={3}>
23
+ {links.map((link) => (
24
+ <Button key={link.label} variant="outline" size="sm" asChild>
25
+ <a href={link.href} target="_blank" rel="noopener noreferrer">
26
+ <ExternalLink className="mr-2 h-3.5 w-3.5" />
27
+ {link.label}
28
+ </a>
29
+ </Button>
30
+ ))}
31
+ </Flex>
32
+ </Stack>
33
+ )
34
+ }
@@ -0,0 +1,20 @@
1
+ import { Stack, Divider } from '@blacksmith-ui/react'
2
+ import { useAuth } from '@/features/auth/hooks/use-auth'
3
+ import { HeroSection } from './components/hero-section'
4
+ import { GettingStarted } from './components/getting-started'
5
+ import { FeaturesGrid } from './components/features-grid'
6
+ import { ResourcesSection } from './components/resources-section'
7
+
8
+ export default function HomePage() {
9
+ const { isAuthenticated } = useAuth()
10
+
11
+ return (
12
+ <Stack gap={12}>
13
+ <HeroSection isAuthenticated={isAuthenticated} />
14
+ <Divider />
15
+ <GettingStarted />
16
+ <FeaturesGrid />
17
+ <ResourcesSection />
18
+ </Stack>
19
+ )
20
+ }
@@ -0,0 +1 @@
1
+ export { homeRoutes } from './routes'
@@ -0,0 +1,7 @@
1
+ import type { RouteObject } from 'react-router-dom'
2
+ import { Path } from '@/router/paths'
3
+ import HomePage from './home'
4
+
5
+ export const homeRoutes: RouteObject[] = [
6
+ { path: Path.Home, element: <HomePage /> },
7
+ ]
@@ -0,0 +1,57 @@
1
+ import { Navigate, useLocation } from 'react-router-dom'
2
+ import { Spinner, Alert, AlertTitle, AlertDescription, Button } from '@blacksmith-ui/react'
3
+ import { ShieldAlert } from 'lucide-react'
4
+ import { useAuth } from '@/features/auth/hooks/use-auth'
5
+ import { Path } from '@/router/paths'
6
+
7
+ interface AuthGuardProps {
8
+ roles?: string[]
9
+ children: React.ReactNode
10
+ }
11
+
12
+ export function AuthGuard({ roles, children }: AuthGuardProps) {
13
+ const { user, isLoading, isAuthenticated } = useAuth()
14
+ const location = useLocation()
15
+
16
+ if (isLoading) {
17
+ return (
18
+ <div className="flex items-center justify-center min-h-[60vh]">
19
+ <Spinner className="h-8 w-8" />
20
+ </div>
21
+ )
22
+ }
23
+
24
+ if (!isAuthenticated) {
25
+ const redirectParam = encodeURIComponent(location.pathname + location.search)
26
+ return <Navigate to={`${Path.Login}?redirect=${redirectParam}`} replace />
27
+ }
28
+
29
+ if (roles && roles.length > 0 && user) {
30
+ const userRoles = (user as any).roles || []
31
+ const hasRole = roles.some((role) => userRoles.includes(role))
32
+ if (!hasRole) {
33
+ return (
34
+ <div className="flex items-center justify-center min-h-[60vh] px-4">
35
+ <div className="w-full max-w-md space-y-4 text-center">
36
+ <div className="flex justify-center">
37
+ <div className="flex h-16 w-16 items-center justify-center rounded-full bg-destructive/10">
38
+ <ShieldAlert className="h-8 w-8 text-destructive" />
39
+ </div>
40
+ </div>
41
+ <Alert variant="destructive">
42
+ <AlertTitle>Access Denied</AlertTitle>
43
+ <AlertDescription>
44
+ You don't have permission to view this page.
45
+ </AlertDescription>
46
+ </Alert>
47
+ <Button variant="outline" onClick={() => window.history.back()}>
48
+ Go Back
49
+ </Button>
50
+ </div>
51
+ </div>
52
+ )
53
+ }
54
+ }
55
+
56
+ return <>{children}</>
57
+ }
@@ -0,0 +1,61 @@
1
+ import { useRouteError, isRouteErrorResponse, useNavigate } from 'react-router-dom'
2
+ import {
3
+ Button,
4
+ Alert,
5
+ AlertTitle,
6
+ AlertDescription,
7
+ Card,
8
+ CardContent,
9
+ } from '@blacksmith-ui/react'
10
+ import { ArrowLeft, RefreshCw, AlertTriangle } from 'lucide-react'
11
+
12
+ export function RouteErrorBoundary() {
13
+ const error = useRouteError()
14
+ const navigate = useNavigate()
15
+
16
+ if (isRouteErrorResponse(error)) {
17
+ return (
18
+ <div className="flex items-center justify-center min-h-screen bg-background">
19
+ <div className="text-center space-y-4">
20
+ <div className="flex justify-center">
21
+ <div className="flex h-20 w-20 items-center justify-center rounded-full bg-muted">
22
+ <AlertTriangle className="h-10 w-10 text-muted-foreground" />
23
+ </div>
24
+ </div>
25
+ <h1 className="text-5xl font-bold tracking-tight">{error.status}</h1>
26
+ <p className="text-xl text-muted-foreground">{error.statusText}</p>
27
+ <div className="pt-2">
28
+ <Button onClick={() => navigate(-1)}>
29
+ <ArrowLeft className="mr-2 h-4 w-4" />
30
+ Go Back
31
+ </Button>
32
+ </div>
33
+ </div>
34
+ </div>
35
+ )
36
+ }
37
+
38
+ return (
39
+ <div className="flex items-center justify-center min-h-screen bg-background px-4">
40
+ <Card className="w-full max-w-md">
41
+ <CardContent className="pt-6 space-y-4">
42
+ <Alert variant="destructive">
43
+ <AlertTriangle className="h-4 w-4" />
44
+ <AlertTitle>Something went wrong</AlertTitle>
45
+ <AlertDescription>
46
+ {error instanceof Error
47
+ ? error.message
48
+ : 'An unexpected error occurred.'}
49
+ </AlertDescription>
50
+ </Alert>
51
+ <div className="flex justify-center">
52
+ <Button onClick={() => window.location.reload()}>
53
+ <RefreshCw className="mr-2 h-4 w-4" />
54
+ Reload Page
55
+ </Button>
56
+ </div>
57
+ </CardContent>
58
+ </Card>
59
+ </div>
60
+ )
61
+ }