doo-boilerplate 0.2.4 → 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/dist/index.js CHANGED
@@ -112,7 +112,8 @@ var FILE_RENAMES = [
112
112
  ["_gitignore", ".gitignore"],
113
113
  ["_env.example", ".env.example"],
114
114
  ["_prettierrc", ".prettierrc"],
115
- ["_prettierignore", ".prettierignore"]
115
+ ["_prettierignore", ".prettierignore"],
116
+ ["_package.json", "package.json"]
116
117
  ];
117
118
  async function scaffold(options, destDir) {
118
119
  const templateSymlink = path2.join(__dirname2, "..", "templates", "template-vite");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doo-boilerplate",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "CLI to scaffold Pila portal frontend projects",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,4 +1,5 @@
1
1
  import { Component, type ErrorInfo, type ReactNode } from 'react'
2
+ import { Link } from '@tanstack/react-router'
2
3
 
3
4
  import { Button } from '@/components/ui/button'
4
5
 
@@ -39,9 +40,14 @@ export class ErrorBoundary extends Component<Props, State> {
39
40
  <p className='max-w-md text-sm text-muted-foreground'>
40
41
  {this.state.error?.message ?? 'An unexpected error occurred.'}
41
42
  </p>
42
- <Button variant='outline' onClick={this.handleReset}>
43
- Try again
44
- </Button>
43
+ <div className='flex gap-3'>
44
+ <Button variant='outline' onClick={this.handleReset}>
45
+ Try again
46
+ </Button>
47
+ <Button asChild>
48
+ <Link to='/500'>View error details</Link>
49
+ </Button>
50
+ </div>
45
51
  </div>
46
52
  )
47
53
  }
@@ -0,0 +1,31 @@
1
+ import { Link } from '@tanstack/react-router'
2
+ import { Button } from '@/components/ui/button'
3
+
4
+ interface ErrorPageProps {
5
+ code: number
6
+ title: string
7
+ description: string
8
+ /** Show 'Go Back' button using history.back() */
9
+ showBack?: boolean
10
+ }
11
+
12
+ /**
13
+ * Reusable full-screen error page for HTTP-style error statuses.
14
+ */
15
+ export function ErrorPage({ code, title, description, showBack = true }: ErrorPageProps) {
16
+ return (
17
+ <div className='flex min-h-screen flex-col items-center justify-center gap-2 text-center px-4'>
18
+ <p className='text-8xl font-bold text-muted-foreground/30'>{code}</p>
19
+ <h1 className='text-2xl font-semibold'>{title}</h1>
20
+ <p className='max-w-md text-muted-foreground'>{description}</p>
21
+ <div className='mt-4 flex gap-3'>
22
+ {showBack && (
23
+ <Button variant='outline' onClick={() => window.history.back()}>Go Back</Button>
24
+ )}
25
+ <Button asChild>
26
+ <Link to='/dashboard'>Go to Dashboard</Link>
27
+ </Button>
28
+ </div>
29
+ </div>
30
+ )
31
+ }
@@ -35,7 +35,7 @@ export function DataTableFacetedFilter<TData, TValue>({
35
35
  options,
36
36
  }: DataTableFacetedFilterProps<TData, TValue>) {
37
37
  const facets = column?.getFacetedUniqueValues()
38
- const selectedValues = new Set(column?.getFilterValue() as string[])
38
+ const selectedValues = new Set((column?.getFilterValue() as string[] | undefined) ?? [])
39
39
 
40
40
  return (
41
41
  <Popover>
@@ -3,18 +3,18 @@ import { Area, AreaChart, CartesianGrid, ResponsiveContainer, Tooltip, XAxis, YA
3
3
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
4
4
 
5
5
  const data = [
6
- { month: 'Jan', revenue: 18000, users: 980 },
7
- { month: 'Feb', revenue: 22000, users: 1200 },
8
- { month: 'Mar', revenue: 19500, users: 1050 },
9
- { month: 'Apr', revenue: 28000, users: 1540 },
10
- { month: 'May', revenue: 24000, users: 1320 },
11
- { month: 'Jun', revenue: 32000, users: 1780 },
12
- { month: 'Jul', revenue: 35000, users: 1960 },
13
- { month: 'Aug', revenue: 29000, users: 1590 },
14
- { month: 'Sep', revenue: 38000, users: 2100 },
15
- { month: 'Oct', revenue: 42000, users: 2300 },
16
- { month: 'Nov', revenue: 39000, users: 2150 },
17
- { month: 'Dec', revenue: 45000, users: 2480 },
6
+ { month: 'Jan', revenue: 18000 },
7
+ { month: 'Feb', revenue: 22000 },
8
+ { month: 'Mar', revenue: 19500 },
9
+ { month: 'Apr', revenue: 28000 },
10
+ { month: 'May', revenue: 24000 },
11
+ { month: 'Jun', revenue: 32000 },
12
+ { month: 'Jul', revenue: 35000 },
13
+ { month: 'Aug', revenue: 29000 },
14
+ { month: 'Sep', revenue: 38000 },
15
+ { month: 'Oct', revenue: 42000 },
16
+ { month: 'Nov', revenue: 39000 },
17
+ { month: 'Dec', revenue: 45000 },
18
18
  ]
19
19
 
20
20
  export function OverviewChart() {
@@ -38,7 +38,7 @@ export function getTaskColumns({ onEdit, onDelete }: ActionsCallbacks): ColumnDe
38
38
  id: 'select',
39
39
  header: ({ table }) => (
40
40
  <Checkbox
41
- checked={table.getIsAllPageRowsSelected()}
41
+ checked={table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && 'indeterminate')}
42
42
  onCheckedChange={(v) => table.toggleAllPageRowsSelected(!!v)}
43
43
  aria-label='Select all'
44
44
  />
@@ -1,4 +1,5 @@
1
1
  import axios from 'axios'
2
+ import { AUTH_STORAGE_KEY } from '@/stores/auth-store'
2
3
 
3
4
  // Axios instance configured with base URL from Vite env
4
5
  const apiClient = axios.create({
@@ -10,7 +11,7 @@ const apiClient = axios.create({
10
11
  // Attach Bearer token from auth storage on each request
11
12
  apiClient.interceptors.request.use((config) => {
12
13
  try {
13
- const stored = localStorage.getItem('auth-storage')
14
+ const stored = localStorage.getItem(AUTH_STORAGE_KEY)
14
15
  if (stored) {
15
16
  const { state } = JSON.parse(stored)
16
17
  if (state?.accessToken) {
@@ -28,7 +29,7 @@ apiClient.interceptors.response.use(
28
29
  (response) => response,
29
30
  (error) => {
30
31
  if (error.response?.status === 401) {
31
- localStorage.removeItem('auth-storage')
32
+ localStorage.removeItem(AUTH_STORAGE_KEY)
32
33
  window.location.href = '/sign-in'
33
34
  }
34
35
  return Promise.reject(error)
@@ -0,0 +1,12 @@
1
+ import { createFileRoute } from '@tanstack/react-router'
2
+ import { ErrorPage } from '@/components/common/error-page'
3
+
4
+ export const Route = createFileRoute('/(errors)/401')({
5
+ component: () => (
6
+ <ErrorPage
7
+ code={401}
8
+ title='Unauthorized'
9
+ description='You need to be logged in to access this page.'
10
+ />
11
+ ),
12
+ })
@@ -0,0 +1,12 @@
1
+ import { createFileRoute } from '@tanstack/react-router'
2
+ import { ErrorPage } from '@/components/common/error-page'
3
+
4
+ export const Route = createFileRoute('/(errors)/403')({
5
+ component: () => (
6
+ <ErrorPage
7
+ code={403}
8
+ title='Forbidden'
9
+ description="You don't have permission to access this resource."
10
+ />
11
+ ),
12
+ })
@@ -1,19 +1,12 @@
1
- import { createFileRoute, Link } from '@tanstack/react-router'
2
-
3
- import { Button } from '@/components/ui/button'
1
+ import { createFileRoute } from '@tanstack/react-router'
2
+ import { ErrorPage } from '@/components/common/error-page'
4
3
 
5
4
  export const Route = createFileRoute('/(errors)/404')({
6
- component: NotFoundPage,
5
+ component: () => (
6
+ <ErrorPage
7
+ code={404}
8
+ title='Page Not Found'
9
+ description="The page you're looking for doesn't exist or has been moved."
10
+ />
11
+ ),
7
12
  })
8
-
9
- function NotFoundPage() {
10
- return (
11
- <div className='flex min-h-screen flex-col items-center justify-center gap-4'>
12
- <h1 className='text-6xl font-bold text-muted-foreground'>404</h1>
13
- <p className='text-lg text-muted-foreground'>Page not found</p>
14
- <Button asChild>
15
- <Link to='/dashboard'>Go to Dashboard</Link>
16
- </Button>
17
- </div>
18
- )
19
- }
@@ -0,0 +1,13 @@
1
+ import { createFileRoute } from '@tanstack/react-router'
2
+ import { ErrorPage } from '@/components/common/error-page'
3
+
4
+ export const Route = createFileRoute('/(errors)/500')({
5
+ component: () => (
6
+ <ErrorPage
7
+ code={500}
8
+ title='Internal Server Error'
9
+ description='Something went wrong on our end. Please try again later.'
10
+ showBack={false}
11
+ />
12
+ ),
13
+ })
@@ -1,27 +1,21 @@
1
- import { Link, createRootRouteWithContext, Outlet } from '@tanstack/react-router'
1
+ import { createRootRouteWithContext, Outlet } from '@tanstack/react-router'
2
2
  import { TanStackRouterDevtools } from '@tanstack/router-devtools'
3
3
 
4
- import { Button } from '@/components/ui/button'
4
+ import { ErrorPage } from '@/components/common/error-page'
5
5
  import type { QueryClient } from '@tanstack/react-query'
6
6
 
7
7
  interface RouterContext {
8
8
  queryClient: QueryClient
9
9
  }
10
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
-
23
11
  export const Route = createRootRouteWithContext<RouterContext>()({
24
- notFoundComponent: NotFound,
12
+ notFoundComponent: () => (
13
+ <ErrorPage
14
+ code={404}
15
+ title='Page Not Found'
16
+ description="The page you're looking for doesn't exist."
17
+ />
18
+ ),
25
19
  component: () => (
26
20
  <>
27
21
  <Outlet />
@@ -1,3 +1,6 @@
1
+ /** Shared storage key used by auth store and api-client interceptor */
2
+ export const AUTH_STORAGE_KEY = 'auth-storage'
3
+
1
4
  import { create } from 'zustand'
2
5
  import { persist } from 'zustand/middleware'
3
6
  import { jwtDecode } from 'jwt-decode'
@@ -51,6 +54,6 @@ export const useAuthStore = create<AuthState>()(
51
54
  hasRole: (role) => get().user?.roles.includes(role) ?? false,
52
55
  hasPermission: (permission) => get().user?.permissions.includes(permission) ?? false,
53
56
  }),
54
- { name: 'auth-storage' }
57
+ { name: AUTH_STORAGE_KEY }
55
58
  )
56
59
  )