doo-boilerplate 0.1.16 → 0.2.2

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 (36) hide show
  1. package/dist/index.js +43 -486
  2. package/package.json +1 -1
  3. package/templates/template-vite/_env.example +8 -0
  4. package/templates/template-vite/package.json +5 -2
  5. package/templates/template-vite/src/components/data-table/data-table-column-header.tsx +62 -0
  6. package/templates/template-vite/src/components/data-table/data-table-faceted-filter.tsx +129 -0
  7. package/templates/template-vite/src/components/data-table/data-table-pagination.tsx +80 -0
  8. package/templates/template-vite/src/components/data-table/data-table-toolbar.tsx +66 -0
  9. package/templates/template-vite/src/components/data-table/data-table-view-options.tsx +46 -0
  10. package/templates/template-vite/src/components/data-table/data-table.tsx +63 -0
  11. package/templates/template-vite/src/components/layout/sidebar.tsx +2 -1
  12. package/templates/template-vite/src/components/ui/alert-dialog.tsx +106 -0
  13. package/templates/template-vite/src/components/ui/command.tsx +118 -0
  14. package/templates/template-vite/src/components/ui/popover.tsx +28 -0
  15. package/templates/template-vite/src/components/ui/table.tsx +77 -0
  16. package/templates/template-vite/src/features/dashboard/components/overview-chart.tsx +61 -0
  17. package/templates/template-vite/src/features/dashboard/components/recent-activity.tsx +37 -0
  18. package/templates/template-vite/src/features/dashboard/components/stats-cards.tsx +37 -0
  19. package/templates/template-vite/src/features/users/components/user-delete-confirmation-dialog.tsx +48 -0
  20. package/templates/template-vite/src/features/users/components/user-form-dialog.tsx +143 -0
  21. package/templates/template-vite/src/features/users/components/users-table-columns.tsx +154 -0
  22. package/templates/template-vite/src/features/users/components/users-table.tsx +143 -0
  23. package/templates/template-vite/src/features/users/data/users-mock-data.ts +55 -0
  24. package/templates/template-vite/src/features/users/schemas/user-form-schema.ts +14 -0
  25. package/templates/template-vite/src/features/users/types/user.ts +12 -0
  26. package/templates/template-vite/src/lib/sentry.ts +28 -0
  27. package/templates/template-vite/src/main.tsx +3 -0
  28. package/templates/template-vite/src/routes/_authenticated/dashboard.tsx +12 -6
  29. package/templates/template-vite/src/routes/_authenticated/users.tsx +16 -0
  30. package/templates/template-vite/vite.config.ts +8 -0
  31. package/templates/template-vite/optional/charts/deps.json +0 -7
  32. package/templates/template-vite/optional/dark-mode/deps.json +0 -5
  33. package/templates/template-vite/optional/dnd/deps.json +0 -8
  34. package/templates/template-vite/optional/editor/deps.json +0 -10
  35. package/templates/template-vite/optional/i18n/deps.json +0 -7
  36. package/templates/template-vite/optional/sentry/deps.json +0 -6
@@ -0,0 +1,154 @@
1
+ import { type ColumnDef } from '@tanstack/react-table'
2
+ import { MoreHorizontal } from 'lucide-react'
3
+
4
+ import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
5
+ import { Badge } from '@/components/ui/badge'
6
+ import { Button } from '@/components/ui/button'
7
+ import { Checkbox } from '@/components/ui/checkbox'
8
+ import {
9
+ DropdownMenu,
10
+ DropdownMenuContent,
11
+ DropdownMenuItem,
12
+ DropdownMenuLabel,
13
+ DropdownMenuSeparator,
14
+ DropdownMenuTrigger,
15
+ } from '@/components/ui/dropdown-menu'
16
+ import { DataTableColumnHeader } from '@/components/data-table/data-table-column-header'
17
+ import type { User } from '../types/user'
18
+
19
+ /** Get initials from a full name for avatar fallback */
20
+ function getInitials(name: string): string {
21
+ return name
22
+ .split(' ')
23
+ .map((n) => n[0])
24
+ .slice(0, 2)
25
+ .join('')
26
+ .toUpperCase()
27
+ }
28
+
29
+ /** Map role value to badge variant */
30
+ const roleBadgeVariant: Record<User['role'], 'default' | 'secondary' | 'outline'> = {
31
+ admin: 'default',
32
+ manager: 'secondary',
33
+ user: 'outline',
34
+ }
35
+
36
+ /** Map status value to badge variant */
37
+ const statusBadgeVariant: Record<User['status'], 'success' | 'destructive' | 'warning'> = {
38
+ active: 'success',
39
+ inactive: 'destructive',
40
+ pending: 'warning',
41
+ }
42
+
43
+ interface GetColumnsOptions {
44
+ onEdit: (user: User) => void
45
+ onDelete: (user: User) => void
46
+ }
47
+
48
+ export function getUsersTableColumns({ onEdit, onDelete }: GetColumnsOptions): ColumnDef<User>[] {
49
+ return [
50
+ {
51
+ id: 'select',
52
+ header: ({ table }) => (
53
+ <Checkbox
54
+ checked={table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && 'indeterminate')}
55
+ onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
56
+ aria-label='Select all'
57
+ />
58
+ ),
59
+ cell: ({ row }) => (
60
+ <Checkbox
61
+ checked={row.getIsSelected()}
62
+ onCheckedChange={(value) => row.toggleSelected(!!value)}
63
+ aria-label='Select row'
64
+ />
65
+ ),
66
+ enableSorting: false,
67
+ enableHiding: false,
68
+ },
69
+ {
70
+ accessorKey: 'name',
71
+ header: ({ column }) => <DataTableColumnHeader column={column} title='Name' />,
72
+ cell: ({ row }) => {
73
+ const user = row.original
74
+ return (
75
+ <div className='flex items-center gap-3'>
76
+ <Avatar className='h-8 w-8'>
77
+ {user.avatar && <AvatarImage src={user.avatar} alt={user.name} />}
78
+ <AvatarFallback className='text-xs'>{getInitials(user.name)}</AvatarFallback>
79
+ </Avatar>
80
+ <span className='font-medium'>{user.name}</span>
81
+ </div>
82
+ )
83
+ },
84
+ },
85
+ {
86
+ accessorKey: 'email',
87
+ header: ({ column }) => <DataTableColumnHeader column={column} title='Email' />,
88
+ cell: ({ row }) => <span className='text-muted-foreground'>{row.getValue('email')}</span>,
89
+ },
90
+ {
91
+ accessorKey: 'role',
92
+ header: ({ column }) => <DataTableColumnHeader column={column} title='Role' />,
93
+ cell: ({ row }) => {
94
+ const role = row.getValue<User['role']>('role')
95
+ return (
96
+ <Badge variant={roleBadgeVariant[role]} className='capitalize'>
97
+ {role}
98
+ </Badge>
99
+ )
100
+ },
101
+ filterFn: 'arrIncludesSome',
102
+ },
103
+ {
104
+ accessorKey: 'status',
105
+ header: ({ column }) => <DataTableColumnHeader column={column} title='Status' />,
106
+ cell: ({ row }) => {
107
+ const status = row.getValue<User['status']>('status')
108
+ return (
109
+ <Badge variant={statusBadgeVariant[status]} className='capitalize'>
110
+ {status}
111
+ </Badge>
112
+ )
113
+ },
114
+ filterFn: 'arrIncludesSome',
115
+ },
116
+ {
117
+ accessorKey: 'createdAt',
118
+ header: ({ column }) => <DataTableColumnHeader column={column} title='Created At' />,
119
+ cell: ({ row }) => (
120
+ <span className='text-muted-foreground'>
121
+ {new Date(row.getValue<string>('createdAt')).toLocaleDateString()}
122
+ </span>
123
+ ),
124
+ },
125
+ {
126
+ id: 'actions',
127
+ enableHiding: false,
128
+ cell: ({ row }) => {
129
+ const user = row.original
130
+ return (
131
+ <DropdownMenu>
132
+ <DropdownMenuTrigger asChild>
133
+ <Button variant='ghost' className='h-8 w-8 p-0'>
134
+ <span className='sr-only'>Open menu</span>
135
+ <MoreHorizontal className='h-4 w-4' />
136
+ </Button>
137
+ </DropdownMenuTrigger>
138
+ <DropdownMenuContent align='end'>
139
+ <DropdownMenuLabel>Actions</DropdownMenuLabel>
140
+ <DropdownMenuSeparator />
141
+ <DropdownMenuItem onClick={() => onEdit(user)}>Edit</DropdownMenuItem>
142
+ <DropdownMenuItem
143
+ onClick={() => onDelete(user)}
144
+ className='text-destructive focus:text-destructive'
145
+ >
146
+ Delete
147
+ </DropdownMenuItem>
148
+ </DropdownMenuContent>
149
+ </DropdownMenu>
150
+ )
151
+ },
152
+ },
153
+ ]
154
+ }
@@ -0,0 +1,143 @@
1
+ import { useState } from 'react'
2
+ import {
3
+ getCoreRowModel,
4
+ getFacetedRowModel,
5
+ getFacetedUniqueValues,
6
+ getFilteredRowModel,
7
+ getPaginationRowModel,
8
+ getSortedRowModel,
9
+ useReactTable,
10
+ type ColumnFiltersState,
11
+ type SortingState,
12
+ type VisibilityState,
13
+ } from '@tanstack/react-table'
14
+ import { PlusCircle } from 'lucide-react'
15
+
16
+ import { Button } from '@/components/ui/button'
17
+ import { DataTable } from '@/components/data-table/data-table'
18
+ import { DataTableToolbar } from '@/components/data-table/data-table-toolbar'
19
+ import { mockUsers } from '../data/users-mock-data'
20
+ import { getUsersTableColumns } from './users-table-columns'
21
+ import { UserFormDialog } from './user-form-dialog'
22
+ import { UserDeleteConfirmationDialog } from './user-delete-confirmation-dialog'
23
+ import type { User } from '../types/user'
24
+
25
+ /** Filter options for role and status faceted filters */
26
+ const roleFilterOptions = [
27
+ { label: 'Admin', value: 'admin' },
28
+ { label: 'Manager', value: 'manager' },
29
+ { label: 'User', value: 'user' },
30
+ ]
31
+
32
+ const statusFilterOptions = [
33
+ { label: 'Active', value: 'active' },
34
+ { label: 'Inactive', value: 'inactive' },
35
+ { label: 'Pending', value: 'pending' },
36
+ ]
37
+
38
+ const toolbarFilters = [
39
+ { columnId: 'role', title: 'Role', options: roleFilterOptions },
40
+ { columnId: 'status', title: 'Status', options: statusFilterOptions },
41
+ ]
42
+
43
+ export function UsersTable() {
44
+ const [data, setData] = useState<User[]>(mockUsers)
45
+ const [sorting, setSorting] = useState<SortingState>([])
46
+ const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
47
+ const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({})
48
+ const [rowSelection, setRowSelection] = useState({})
49
+
50
+ // Dialog state
51
+ const [formDialogOpen, setFormDialogOpen] = useState(false)
52
+ const [deleteDialogOpen, setDeleteDialogOpen] = useState(false)
53
+ const [selectedUser, setSelectedUser] = useState<User | null>(null)
54
+
55
+ const handleEdit = (user: User) => {
56
+ setSelectedUser(user)
57
+ setFormDialogOpen(true)
58
+ }
59
+
60
+ const handleDelete = (user: User) => {
61
+ setSelectedUser(user)
62
+ setDeleteDialogOpen(true)
63
+ }
64
+
65
+ const handleAddNew = () => {
66
+ setSelectedUser(null)
67
+ setFormDialogOpen(true)
68
+ }
69
+
70
+ const handleFormSubmit = (values: Omit<User, 'id' | 'createdAt'>) => {
71
+ if (selectedUser) {
72
+ // Edit existing user
73
+ setData((prev) => prev.map((u) => (u.id === selectedUser.id ? { ...u, ...values } : u)))
74
+ } else {
75
+ // Add new user
76
+ const newUser: User = {
77
+ ...values,
78
+ id: String(Date.now()),
79
+ createdAt: new Date().toISOString().split('T')[0],
80
+ }
81
+ setData((prev) => [newUser, ...prev])
82
+ }
83
+ setFormDialogOpen(false)
84
+ }
85
+
86
+ const handleConfirmDelete = () => {
87
+ if (selectedUser) {
88
+ setData((prev) => prev.filter((u) => u.id !== selectedUser.id))
89
+ }
90
+ setDeleteDialogOpen(false)
91
+ setSelectedUser(null)
92
+ }
93
+
94
+ const columns = getUsersTableColumns({ onEdit: handleEdit, onDelete: handleDelete })
95
+
96
+ const table = useReactTable({
97
+ data,
98
+ columns,
99
+ state: { sorting, columnFilters, columnVisibility, rowSelection },
100
+ onSortingChange: setSorting,
101
+ onColumnFiltersChange: setColumnFilters,
102
+ onColumnVisibilityChange: setColumnVisibility,
103
+ onRowSelectionChange: setRowSelection,
104
+ getCoreRowModel: getCoreRowModel(),
105
+ getFilteredRowModel: getFilteredRowModel(),
106
+ getSortedRowModel: getSortedRowModel(),
107
+ getPaginationRowModel: getPaginationRowModel(),
108
+ getFacetedRowModel: getFacetedRowModel(),
109
+ getFacetedUniqueValues: getFacetedUniqueValues(),
110
+ })
111
+
112
+ return (
113
+ <div className='space-y-4'>
114
+ <div className='flex items-center justify-between'>
115
+ <DataTableToolbar
116
+ table={table}
117
+ searchColumn='name'
118
+ searchPlaceholder='Search users...'
119
+ filters={toolbarFilters}
120
+ />
121
+ <Button size='sm' className='ml-4' onClick={handleAddNew}>
122
+ <PlusCircle className='mr-2 h-4 w-4' />
123
+ Add User
124
+ </Button>
125
+ </div>
126
+ <DataTable table={table} columns={columns} />
127
+
128
+ <UserFormDialog
129
+ open={formDialogOpen}
130
+ onOpenChange={setFormDialogOpen}
131
+ user={selectedUser}
132
+ onSubmit={handleFormSubmit}
133
+ />
134
+
135
+ <UserDeleteConfirmationDialog
136
+ open={deleteDialogOpen}
137
+ onOpenChange={setDeleteDialogOpen}
138
+ user={selectedUser}
139
+ onConfirm={handleConfirmDelete}
140
+ />
141
+ </div>
142
+ )
143
+ }
@@ -0,0 +1,55 @@
1
+ import type { User } from '../types/user'
2
+
3
+ /** 50 mock users covering all roles (admin/manager/user) and statuses (active/inactive/pending) */
4
+ export const mockUsers: User[] = [
5
+ { id: '1', name: 'Alice Johnson', email: 'alice.johnson@example.com', role: 'admin', status: 'active', createdAt: '2023-01-15' },
6
+ { id: '2', name: 'Bob Smith', email: 'bob.smith@example.com', role: 'manager', status: 'active', createdAt: '2023-01-20' },
7
+ { id: '3', name: 'Carol Williams', email: 'carol.williams@example.com', role: 'user', status: 'active', createdAt: '2023-02-05' },
8
+ { id: '4', name: 'David Brown', email: 'david.brown@example.com', role: 'user', status: 'inactive', createdAt: '2023-02-10' },
9
+ { id: '5', name: 'Eva Martinez', email: 'eva.martinez@example.com', role: 'manager', status: 'pending', createdAt: '2023-02-18' },
10
+ { id: '6', name: 'Frank Davis', email: 'frank.davis@example.com', role: 'user', status: 'active', createdAt: '2023-03-01' },
11
+ { id: '7', name: 'Grace Wilson', email: 'grace.wilson@example.com', role: 'admin', status: 'active', createdAt: '2023-03-12' },
12
+ { id: '8', name: 'Henry Taylor', email: 'henry.taylor@example.com', role: 'user', status: 'pending', createdAt: '2023-03-20' },
13
+ { id: '9', name: 'Iris Anderson', email: 'iris.anderson@example.com', role: 'manager', status: 'active', createdAt: '2023-04-02' },
14
+ { id: '10', name: 'Jack Thomas', email: 'jack.thomas@example.com', role: 'user', status: 'inactive', createdAt: '2023-04-15' },
15
+ { id: '11', name: 'Karen Jackson', email: 'karen.jackson@example.com', role: 'user', status: 'active', createdAt: '2023-04-22' },
16
+ { id: '12', name: 'Leo White', email: 'leo.white@example.com', role: 'manager', status: 'inactive', createdAt: '2023-05-03' },
17
+ { id: '13', name: 'Mia Harris', email: 'mia.harris@example.com', role: 'user', status: 'active', createdAt: '2023-05-14' },
18
+ { id: '14', name: 'Nathan Martin', email: 'nathan.martin@example.com', role: 'admin', status: 'active', createdAt: '2023-05-25' },
19
+ { id: '15', name: 'Olivia Garcia', email: 'olivia.garcia@example.com', role: 'user', status: 'pending', createdAt: '2023-06-08' },
20
+ { id: '16', name: 'Paul Rodriguez', email: 'paul.rodriguez@example.com', role: 'user', status: 'active', createdAt: '2023-06-15' },
21
+ { id: '17', name: 'Quinn Lewis', email: 'quinn.lewis@example.com', role: 'manager', status: 'active', createdAt: '2023-06-28' },
22
+ { id: '18', name: 'Rachel Lee', email: 'rachel.lee@example.com', role: 'user', status: 'inactive', createdAt: '2023-07-05' },
23
+ { id: '19', name: 'Sam Walker', email: 'sam.walker@example.com', role: 'user', status: 'active', createdAt: '2023-07-18' },
24
+ { id: '20', name: 'Tina Hall', email: 'tina.hall@example.com', role: 'admin', status: 'pending', createdAt: '2023-07-30' },
25
+ { id: '21', name: 'Uma Allen', email: 'uma.allen@example.com', role: 'user', status: 'active', createdAt: '2023-08-10' },
26
+ { id: '22', name: 'Victor Young', email: 'victor.young@example.com', role: 'manager', status: 'active', createdAt: '2023-08-22' },
27
+ { id: '23', name: 'Wendy Hernandez', email: 'wendy.hernandez@example.com', role: 'user', status: 'active', createdAt: '2023-09-01' },
28
+ { id: '24', name: 'Xander King', email: 'xander.king@example.com', role: 'user', status: 'inactive', createdAt: '2023-09-14' },
29
+ { id: '25', name: 'Yara Wright', email: 'yara.wright@example.com', role: 'manager', status: 'pending', createdAt: '2023-09-25' },
30
+ { id: '26', name: 'Zoe Lopez', email: 'zoe.lopez@example.com', role: 'user', status: 'active', createdAt: '2023-10-05' },
31
+ { id: '27', name: 'Aaron Hill', email: 'aaron.hill@example.com', role: 'admin', status: 'active', createdAt: '2023-10-18' },
32
+ { id: '28', name: 'Bella Scott', email: 'bella.scott@example.com', role: 'user', status: 'active', createdAt: '2023-10-30' },
33
+ { id: '29', name: 'Carlos Green', email: 'carlos.green@example.com', role: 'user', status: 'pending', createdAt: '2023-11-08' },
34
+ { id: '30', name: 'Diana Adams', email: 'diana.adams@example.com', role: 'manager', status: 'active', createdAt: '2023-11-20' },
35
+ { id: '31', name: 'Ethan Baker', email: 'ethan.baker@example.com', role: 'user', status: 'inactive', createdAt: '2023-12-01' },
36
+ { id: '32', name: 'Fiona Gonzalez', email: 'fiona.gonzalez@example.com', role: 'user', status: 'active', createdAt: '2023-12-12' },
37
+ { id: '33', name: 'George Nelson', email: 'george.nelson@example.com', role: 'manager', status: 'active', createdAt: '2023-12-24' },
38
+ { id: '34', name: 'Hannah Carter', email: 'hannah.carter@example.com', role: 'admin', status: 'active', createdAt: '2024-01-05' },
39
+ { id: '35', name: 'Ian Mitchell', email: 'ian.mitchell@example.com', role: 'user', status: 'pending', createdAt: '2024-01-18' },
40
+ { id: '36', name: 'Julia Perez', email: 'julia.perez@example.com', role: 'user', status: 'active', createdAt: '2024-01-30' },
41
+ { id: '37', name: 'Kevin Roberts', email: 'kevin.roberts@example.com', role: 'manager', status: 'inactive', createdAt: '2024-02-10' },
42
+ { id: '38', name: 'Laura Turner', email: 'laura.turner@example.com', role: 'user', status: 'active', createdAt: '2024-02-22' },
43
+ { id: '39', name: 'Mike Phillips', email: 'mike.phillips@example.com', role: 'user', status: 'active', createdAt: '2024-03-05' },
44
+ { id: '40', name: 'Nina Campbell', email: 'nina.campbell@example.com', role: 'admin', status: 'active', createdAt: '2024-03-15' },
45
+ { id: '41', name: 'Oscar Parker', email: 'oscar.parker@example.com', role: 'user', status: 'inactive', createdAt: '2024-03-28' },
46
+ { id: '42', name: 'Penny Evans', email: 'penny.evans@example.com', role: 'manager', status: 'active', createdAt: '2024-04-08' },
47
+ { id: '43', name: 'Rick Edwards', email: 'rick.edwards@example.com', role: 'user', status: 'pending', createdAt: '2024-04-20' },
48
+ { id: '44', name: 'Sara Collins', email: 'sara.collins@example.com', role: 'user', status: 'active', createdAt: '2024-05-01' },
49
+ { id: '45', name: 'Tom Stewart', email: 'tom.stewart@example.com', role: 'manager', status: 'active', createdAt: '2024-05-14' },
50
+ { id: '46', name: 'Ursula Morris', email: 'ursula.morris@example.com', role: 'user', status: 'active', createdAt: '2024-05-25' },
51
+ { id: '47', name: 'Vince Rogers', email: 'vince.rogers@example.com', role: 'admin', status: 'inactive', createdAt: '2024-06-05' },
52
+ { id: '48', name: 'Wanda Reed', email: 'wanda.reed@example.com', role: 'user', status: 'active', createdAt: '2024-06-18' },
53
+ { id: '49', name: 'Xavier Cook', email: 'xavier.cook@example.com', role: 'user', status: 'pending', createdAt: '2024-06-28' },
54
+ { id: '50', name: 'Yvonne Morgan', email: 'yvonne.morgan@example.com', role: 'manager', status: 'active', createdAt: '2024-07-10' },
55
+ ]
@@ -0,0 +1,14 @@
1
+ import { z } from 'zod'
2
+
3
+ export const userFormSchema = z.object({
4
+ name: z.string().min(2, 'Name must be at least 2 characters').max(100, 'Name must be at most 100 characters'),
5
+ email: z.string().email('Please enter a valid email address'),
6
+ role: z.enum(['admin', 'manager', 'user'], {
7
+ required_error: 'Please select a role',
8
+ }),
9
+ status: z.enum(['active', 'inactive', 'pending'], {
10
+ required_error: 'Please select a status',
11
+ }),
12
+ })
13
+
14
+ export type UserFormValues = z.infer<typeof userFormSchema>
@@ -0,0 +1,12 @@
1
+ export type UserRole = 'admin' | 'manager' | 'user'
2
+ export type UserStatus = 'active' | 'inactive' | 'pending'
3
+
4
+ export interface User {
5
+ id: string
6
+ name: string
7
+ email: string
8
+ role: UserRole
9
+ status: UserStatus
10
+ createdAt: string
11
+ avatar?: string
12
+ }
@@ -0,0 +1,28 @@
1
+ import * as Sentry from '@sentry/react'
2
+
3
+ /**
4
+ * Initialize Sentry error tracking.
5
+ * Only runs if VITE_SENTRY_DSN is set.
6
+ */
7
+ export function initSentry() {
8
+ const dsn = import.meta.env.VITE_SENTRY_DSN
9
+ if (!dsn) return
10
+
11
+ Sentry.init({
12
+ dsn,
13
+ environment: import.meta.env.MODE,
14
+ // Capture 10% of transactions for performance monitoring
15
+ tracesSampleRate: import.meta.env.PROD ? 0.1 : 1.0,
16
+ // Only send errors in production and staging
17
+ enabled: import.meta.env.PROD || import.meta.env.MODE === 'staging',
18
+ integrations: [
19
+ Sentry.browserTracingIntegration(),
20
+ Sentry.replayIntegration({
21
+ maskAllText: false,
22
+ blockAllMedia: false,
23
+ }),
24
+ ],
25
+ replaysSessionSampleRate: 0.1,
26
+ replaysOnErrorSampleRate: 1.0,
27
+ })
28
+ }
@@ -1,3 +1,4 @@
1
+ import { initSentry } from '@/lib/sentry'
1
2
  import '@/lib/i18n'
2
3
  import { StrictMode } from 'react'
3
4
  import { createRoot } from 'react-dom/client'
@@ -11,6 +12,8 @@ import { queryClient } from './lib/query-client'
11
12
  import { ThemeProvider } from './context/theme-provider'
12
13
  import './styles/globals.css'
13
14
 
15
+ initSentry()
16
+
14
17
  const router = createRouter({
15
18
  routeTree,
16
19
  context: { queryClient },
@@ -1,6 +1,9 @@
1
1
  import { createFileRoute } from '@tanstack/react-router'
2
2
 
3
3
  import { PageLayout } from '@/components/layout/page-layout'
4
+ import { OverviewChart } from '@/features/dashboard/components/overview-chart'
5
+ import { RecentActivity } from '@/features/dashboard/components/recent-activity'
6
+ import { StatsCards } from '@/features/dashboard/components/stats-cards'
4
7
 
5
8
  export const Route = createFileRoute('/_authenticated/dashboard')({
6
9
  component: DashboardPage,
@@ -9,13 +12,16 @@ export const Route = createFileRoute('/_authenticated/dashboard')({
9
12
  function DashboardPage() {
10
13
  return (
11
14
  <PageLayout title='Dashboard' description='Welcome to your portal'>
12
- <div className='grid gap-4 md:grid-cols-2 lg:grid-cols-4'>
13
- {['Total Users', 'Active Sessions', 'Revenue', 'Growth'].map((label) => (
14
- <div key={label} className='rounded-lg border bg-card p-6'>
15
- <p className='text-sm text-muted-foreground'>{label}</p>
16
- <p className='mt-2 text-2xl font-bold'>—</p>
15
+ <div className='space-y-6'>
16
+ <StatsCards />
17
+ <div className='grid gap-6 lg:grid-cols-7'>
18
+ <div className='lg:col-span-4'>
19
+ <OverviewChart />
17
20
  </div>
18
- ))}
21
+ <div className='lg:col-span-3'>
22
+ <RecentActivity />
23
+ </div>
24
+ </div>
19
25
  </div>
20
26
  </PageLayout>
21
27
  )
@@ -0,0 +1,16 @@
1
+ import { createFileRoute } from '@tanstack/react-router'
2
+
3
+ import { PageLayout } from '@/components/layout/page-layout'
4
+ import { UsersTable } from '@/features/users/components/users-table'
5
+
6
+ export const Route = createFileRoute('/_authenticated/users')({
7
+ component: UsersPage,
8
+ })
9
+
10
+ function UsersPage() {
11
+ return (
12
+ <PageLayout title='User Management' description='Manage your application users'>
13
+ <UsersTable />
14
+ </PageLayout>
15
+ )
16
+ }
@@ -2,6 +2,7 @@ import { defineConfig } from 'vite'
2
2
  import react from '@vitejs/plugin-react-swc'
3
3
  import tailwindcss from '@tailwindcss/vite'
4
4
  import { TanStackRouterVite } from '@tanstack/router-plugin/vite'
5
+ import { sentryVitePlugin } from '@sentry/vite-plugin'
5
6
  import { resolve } from 'path'
6
7
 
7
8
  export default defineConfig({
@@ -9,11 +10,18 @@ export default defineConfig({
9
10
  TanStackRouterVite({ autoCodeSplitting: true }),
10
11
  react(),
11
12
  tailwindcss(),
13
+ // Only upload source maps in production builds with SENTRY_AUTH_TOKEN set
14
+ ...(process.env.SENTRY_AUTH_TOKEN ? [sentryVitePlugin({
15
+ org: process.env.SENTRY_ORG,
16
+ project: process.env.SENTRY_PROJECT,
17
+ authToken: process.env.SENTRY_AUTH_TOKEN,
18
+ })] : []),
12
19
  ],
13
20
  resolve: {
14
21
  alias: { '@': resolve(__dirname, './src') },
15
22
  },
16
23
  build: {
24
+ sourcemap: true,
17
25
  target: 'esnext',
18
26
  rolldownOptions: {
19
27
  output: {
@@ -1,7 +0,0 @@
1
- {
2
- "dependencies": {
3
- "echarts": "^6.0.0",
4
- "echarts-for-react": "^3.0.2",
5
- "recharts": "^2.15.1"
6
- }
7
- }
@@ -1,5 +0,0 @@
1
- {
2
- "dependencies": {
3
- "next-themes": "^0.4.6"
4
- }
5
- }
@@ -1,8 +0,0 @@
1
- {
2
- "dependencies": {
3
- "@dnd-kit/core": "^6.3.1",
4
- "@dnd-kit/sortable": "^8.0.0",
5
- "@dnd-kit/modifiers": "^7.0.0",
6
- "@dnd-kit/utilities": "^3.2.2"
7
- }
8
- }
@@ -1,10 +0,0 @@
1
- {
2
- "dependencies": {
3
- "@tiptap/react": "^2.11.7",
4
- "@tiptap/starter-kit": "^2.11.7",
5
- "@tiptap/extension-link": "^2.11.7",
6
- "@tiptap/extension-image": "^2.11.7",
7
- "@tiptap/extension-placeholder": "^2.11.7",
8
- "@tiptap/extension-underline": "^2.11.7"
9
- }
10
- }
@@ -1,7 +0,0 @@
1
- {
2
- "dependencies": {
3
- "i18next": "^24.2.3",
4
- "react-i18next": "^15.4.1",
5
- "i18next-browser-languagedetector": "^8.0.4"
6
- }
7
- }
@@ -1,6 +0,0 @@
1
- {
2
- "dependencies": {
3
- "@sentry/react": "^9.19.0",
4
- "@sentry/vite-plugin": "^2.22.7"
5
- }
6
- }