doo-boilerplate 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doo-boilerplate",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "CLI to scaffold Pila portal frontend projects",
5
5
  "type": "module",
6
6
  "bin": {
@@ -40,7 +40,7 @@ export function SignInForm() {
40
40
  <Card className="w-full max-w-md">
41
41
  <CardHeader className="space-y-1">
42
42
  <CardTitle className="text-2xl font-bold">Sign in</CardTitle>
43
- <CardDescription>Enter your credentials to access your account · or try <strong>demo / demo</strong></CardDescription>
43
+ <CardDescription>Enter your credentials to access your account · or try <strong>demo@gmail.com / demo</strong></CardDescription>
44
44
  </CardHeader>
45
45
  <CardContent>
46
46
  <Form {...form}>
@@ -1,8 +1,8 @@
1
1
  import { z } from 'zod'
2
2
 
3
3
  export const signInSchema = z.object({
4
- email: z.union([z.string().email('Invalid email address'), z.literal('demo')]),
5
- password: z.union([z.string().min(6, 'Password must be at least 6 characters'), z.literal('demo')]),
4
+ email: z.string().email('Invalid email address'),
5
+ password: z.string().min(1, 'Password is required'),
6
6
  })
7
7
 
8
8
  export const signUpSchema = z
@@ -24,7 +24,7 @@ function makeDemoToken(): string {
24
24
  }
25
25
 
26
26
  const DEMO_RESPONSE: LoginResponse = {
27
- user: { id: 'demo', email: 'demo', name: 'Demo User', roles: ['admin'], permissions: ['*'] },
27
+ user: { id: 'demo', email: 'demo@gmail.com', name: 'Demo User', roles: ['admin'], permissions: ['*'] },
28
28
  accessToken: makeDemoToken(),
29
29
  refreshToken: 'demo-refresh',
30
30
  }
@@ -32,7 +32,7 @@ const DEMO_RESPONSE: LoginResponse = {
32
32
  export const authApi = {
33
33
  login: (data: SignInValues): Promise<LoginResponse> => {
34
34
  // Demo account — works without a real backend
35
- if (data.email === 'demo' && data.password === 'demo') {
35
+ if (data.email === 'demo@gmail.com' && data.password === 'demo') {
36
36
  return Promise.resolve(DEMO_RESPONSE)
37
37
  }
38
38
  return apiClient.post<LoginResponse>('/auth/login', data).then((r) => r.data)
@@ -25,7 +25,7 @@ export function SignInForm() {
25
25
  <div className='text-center'>
26
26
  <h1 className='text-2xl font-bold tracking-tight'>Sign in</h1>
27
27
  <p className='mt-1 text-sm text-muted-foreground'>
28
- Enter your credentials to access your account · or try <strong>demo / demo</strong>
28
+ Enter your credentials to access your account · or try <strong>demo@gmail.com / demo</strong>
29
29
  </p>
30
30
  </div>
31
31
 
@@ -1,5 +1,5 @@
1
1
  import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
2
- import { useNavigate } from '@tanstack/react-router'
2
+ import { useNavigate, useRouter } from '@tanstack/react-router'
3
3
 
4
4
  import { useAuthStore } from '@/stores/auth-store'
5
5
 
@@ -9,11 +9,14 @@ import { authApi } from '../services/auth-api'
9
9
  export function useSignIn() {
10
10
  const { login } = useAuthStore()
11
11
  const navigate = useNavigate()
12
+ const router = useRouter()
12
13
 
13
14
  return useMutation({
14
15
  mutationFn: (data: SignInValues) => authApi.login(data),
15
- onSuccess: (data) => {
16
+ onSuccess: async (data) => {
16
17
  login(data.user, data.accessToken, data.refreshToken)
18
+ // Invalidate router so _authenticated.beforeLoad re-evaluates with new auth state
19
+ await router.invalidate()
17
20
  navigate({ to: '/dashboard' })
18
21
  },
19
22
  })
@@ -22,13 +25,15 @@ export function useSignIn() {
22
25
  export function useSignOut() {
23
26
  const { logout } = useAuthStore()
24
27
  const navigate = useNavigate()
28
+ const router = useRouter()
25
29
  const qc = useQueryClient()
26
30
 
27
31
  return useMutation({
28
32
  mutationFn: () => authApi.logout(),
29
- onSettled: () => {
33
+ onSettled: async () => {
30
34
  logout()
31
35
  qc.clear()
36
+ await router.invalidate()
32
37
  navigate({ to: '/sign-in' })
33
38
  },
34
39
  })
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod'
2
2
 
3
3
  export const signInSchema = z.object({
4
- email: z.union([z.string().email('Please enter a valid email address'), z.literal('demo')]),
4
+ email: z.string().email('Please enter a valid email address'),
5
5
  password: z.string().min(1, 'Password is required'),
6
6
  })
7
7
 
@@ -24,7 +24,7 @@ function makeDemoToken(): string {
24
24
  }
25
25
 
26
26
  const DEMO_RESPONSE: LoginResponse = {
27
- user: { id: 'demo', email: 'demo', name: 'Demo User', roles: ['admin'], permissions: ['*'] },
27
+ user: { id: 'demo', email: 'demo@gmail.com', name: 'Demo User', roles: ['admin'], permissions: ['*'] },
28
28
  accessToken: makeDemoToken(),
29
29
  refreshToken: 'demo-refresh',
30
30
  }
@@ -32,7 +32,7 @@ const DEMO_RESPONSE: LoginResponse = {
32
32
  export const authApi = {
33
33
  login: (data: SignInValues): Promise<LoginResponse> => {
34
34
  // Demo account — works without a real backend
35
- if (data.email === 'demo' && data.password === 'demo') {
35
+ if (data.email === 'demo@gmail.com' && data.password === 'demo') {
36
36
  return Promise.resolve(DEMO_RESPONSE)
37
37
  }
38
38
  return apiClient.post<LoginResponse>('/auth/login', data).then((r) => r.data)
@@ -1,8 +1,15 @@
1
- import { createFileRoute } from '@tanstack/react-router'
1
+ import { createFileRoute, redirect } from '@tanstack/react-router'
2
2
 
3
3
  import { SignInForm } from '@/features/auth/components/sign-in-form'
4
+ import { useAuthStore } from '@/stores/auth-store'
4
5
 
5
6
  export const Route = createFileRoute('/(auth)/sign-in')({
7
+ beforeLoad: () => {
8
+ const { isAuthenticated, isTokenExpired } = useAuthStore.getState()
9
+ if (isAuthenticated && !isTokenExpired()) {
10
+ throw redirect({ to: '/dashboard' })
11
+ }
12
+ },
6
13
  component: SignInPage,
7
14
  })
8
15
 
@@ -1,13 +1,27 @@
1
- import { createRootRouteWithContext, Outlet } from '@tanstack/react-router'
1
+ import { Link, createRootRouteWithContext, Outlet } from '@tanstack/react-router'
2
2
  import { TanStackRouterDevtools } from '@tanstack/router-devtools'
3
3
 
4
+ import { Button } from '@/components/ui/button'
4
5
  import type { QueryClient } from '@tanstack/react-query'
5
6
 
6
7
  interface RouterContext {
7
8
  queryClient: QueryClient
8
9
  }
9
10
 
11
+ function NotFound() {
12
+ return (
13
+ <div className='flex min-h-screen flex-col items-center justify-center gap-4'>
14
+ <h1 className='text-6xl font-bold text-muted-foreground'>404</h1>
15
+ <p className='text-lg text-muted-foreground'>Page not found</p>
16
+ <Button asChild>
17
+ <Link to='/dashboard'>Go to Dashboard</Link>
18
+ </Button>
19
+ </div>
20
+ )
21
+ }
22
+
10
23
  export const Route = createRootRouteWithContext<RouterContext>()({
24
+ notFoundComponent: NotFound,
11
25
  component: () => (
12
26
  <>
13
27
  <Outlet />
@@ -0,0 +1,7 @@
1
+ import { createFileRoute, redirect } from '@tanstack/react-router'
2
+
3
+ export const Route = createFileRoute('/')({
4
+ beforeLoad: () => {
5
+ throw redirect({ to: '/dashboard' })
6
+ },
7
+ })