87 | >(({ className, ...props }, ref) => (
88 | [role=checkbox]]:translate-y-[2px]",
92 | className
93 | )}
94 | {...props}
95 | />
96 | ))
97 | TableCell.displayName = "TableCell"
98 |
99 | const TableCaption = React.forwardRef<
100 | HTMLTableCaptionElement,
101 | React.HTMLAttributes
102 | >(({ className, ...props }, ref) => (
103 |
108 | ))
109 | TableCaption.displayName = "TableCaption"
110 |
111 | export {
112 | Table,
113 | TableHeader,
114 | TableBody,
115 | TableFooter,
116 | TableHead,
117 | TableRow,
118 | TableCell,
119 | TableCaption,
120 | }
121 |
--------------------------------------------------------------------------------
/src/components/ui/tabs.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as TabsPrimitive from "@radix-ui/react-tabs"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Tabs = TabsPrimitive.Root
7 |
8 | const TabsList = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 | ))
21 | TabsList.displayName = TabsPrimitive.List.displayName
22 |
23 | const TabsTrigger = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, ...props }, ref) => (
27 |
35 | ))
36 | TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
37 |
38 | const TabsContent = React.forwardRef<
39 | React.ElementRef,
40 | React.ComponentPropsWithoutRef
41 | >(({ className, ...props }, ref) => (
42 |
50 | ))
51 | TabsContent.displayName = TabsPrimitive.Content.displayName
52 |
53 | export { Tabs, TabsList, TabsTrigger, TabsContent }
54 |
--------------------------------------------------------------------------------
/src/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface TextareaProps
6 | extends React.TextareaHTMLAttributes {}
7 |
8 | const Textarea = React.forwardRef(
9 | ({ className, ...props }, ref) => {
10 | return (
11 |
19 | )
20 | }
21 | )
22 | Textarea.displayName = "Textarea"
23 |
24 | export { Textarea }
25 |
--------------------------------------------------------------------------------
/src/components/ui/toast.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Cross2Icon } from "@radix-ui/react-icons"
3 | import * as ToastPrimitives from "@radix-ui/react-toast"
4 | import { cva, type VariantProps } from "class-variance-authority"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const ToastProvider = ToastPrimitives.Provider
9 |
10 | const ToastViewport = React.forwardRef<
11 | React.ElementRef,
12 | React.ComponentPropsWithoutRef
13 | >(({ className, ...props }, ref) => (
14 |
22 | ))
23 | ToastViewport.displayName = ToastPrimitives.Viewport.displayName
24 |
25 | const toastVariants = cva(
26 | "group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
27 | {
28 | variants: {
29 | variant: {
30 | default: "border bg-background text-foreground",
31 | destructive:
32 | "destructive group border-destructive bg-destructive text-destructive-foreground",
33 | },
34 | },
35 | defaultVariants: {
36 | variant: "default",
37 | },
38 | }
39 | )
40 |
41 | const Toast = React.forwardRef<
42 | React.ElementRef,
43 | React.ComponentPropsWithoutRef &
44 | VariantProps
45 | >(({ className, variant, ...props }, ref) => {
46 | return (
47 |
52 | )
53 | })
54 | Toast.displayName = ToastPrimitives.Root.displayName
55 |
56 | const ToastAction = React.forwardRef<
57 | React.ElementRef,
58 | React.ComponentPropsWithoutRef
59 | >(({ className, ...props }, ref) => (
60 |
68 | ))
69 | ToastAction.displayName = ToastPrimitives.Action.displayName
70 |
71 | const ToastClose = React.forwardRef<
72 | React.ElementRef,
73 | React.ComponentPropsWithoutRef
74 | >(({ className, ...props }, ref) => (
75 |
84 |
85 |
86 | ))
87 | ToastClose.displayName = ToastPrimitives.Close.displayName
88 |
89 | const ToastTitle = React.forwardRef<
90 | React.ElementRef,
91 | React.ComponentPropsWithoutRef
92 | >(({ className, ...props }, ref) => (
93 |
98 | ))
99 | ToastTitle.displayName = ToastPrimitives.Title.displayName
100 |
101 | const ToastDescription = React.forwardRef<
102 | React.ElementRef,
103 | React.ComponentPropsWithoutRef
104 | >(({ className, ...props }, ref) => (
105 |
110 | ))
111 | ToastDescription.displayName = ToastPrimitives.Description.displayName
112 |
113 | type ToastProps = React.ComponentPropsWithoutRef
114 |
115 | type ToastActionElement = React.ReactElement
116 |
117 | export {
118 | type ToastProps,
119 | type ToastActionElement,
120 | ToastProvider,
121 | ToastViewport,
122 | Toast,
123 | ToastTitle,
124 | ToastDescription,
125 | ToastClose,
126 | ToastAction,
127 | }
128 |
--------------------------------------------------------------------------------
/src/components/ui/toaster.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Toast,
3 | ToastClose,
4 | ToastDescription,
5 | ToastProvider,
6 | ToastTitle,
7 | ToastViewport,
8 | } from "@/components/ui/toast"
9 | import { useToast } from "@/components/ui/use-toast"
10 |
11 | export function Toaster() {
12 | const { toasts } = useToast()
13 |
14 | return (
15 |
16 | {toasts.map(function ({ id, title, description, action, ...props }) {
17 | return (
18 |
19 |
20 | {title && {title}}
21 | {description && (
22 | {description}
23 | )}
24 |
25 | {action}
26 |
27 |
28 | )
29 | })}
30 |
31 |
32 | )
33 | }
34 |
--------------------------------------------------------------------------------
/src/components/ui/tooltip.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as TooltipPrimitive from "@radix-ui/react-tooltip"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const TooltipProvider = TooltipPrimitive.Provider
7 |
8 | const Tooltip = TooltipPrimitive.Root
9 |
10 | const TooltipTrigger = TooltipPrimitive.Trigger
11 |
12 | const TooltipContent = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, sideOffset = 4, ...props }, ref) => (
16 |
25 | ))
26 | TooltipContent.displayName = TooltipPrimitive.Content.displayName
27 |
28 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
29 |
--------------------------------------------------------------------------------
/src/components/ui/use-toast.ts:
--------------------------------------------------------------------------------
1 | // Inspired by react-hot-toast library
2 | import * as React from "react"
3 |
4 | import type {
5 | ToastActionElement,
6 | ToastProps,
7 | } from "@/components/ui/toast"
8 |
9 | const TOAST_LIMIT = 1
10 | const TOAST_REMOVE_DELAY = 1000000
11 |
12 | type ToasterToast = ToastProps & {
13 | id: string
14 | title?: React.ReactNode
15 | description?: React.ReactNode
16 | action?: ToastActionElement
17 | }
18 |
19 | const actionTypes = {
20 | ADD_TOAST: "ADD_TOAST",
21 | UPDATE_TOAST: "UPDATE_TOAST",
22 | DISMISS_TOAST: "DISMISS_TOAST",
23 | REMOVE_TOAST: "REMOVE_TOAST",
24 | } as const
25 |
26 | let count = 0
27 |
28 | function genId() {
29 | count = (count + 1) % Number.MAX_SAFE_INTEGER
30 | return count.toString()
31 | }
32 |
33 | type ActionType = typeof actionTypes
34 |
35 | type Action =
36 | | {
37 | type: ActionType["ADD_TOAST"]
38 | toast: ToasterToast
39 | }
40 | | {
41 | type: ActionType["UPDATE_TOAST"]
42 | toast: Partial
43 | }
44 | | {
45 | type: ActionType["DISMISS_TOAST"]
46 | toastId?: ToasterToast["id"]
47 | }
48 | | {
49 | type: ActionType["REMOVE_TOAST"]
50 | toastId?: ToasterToast["id"]
51 | }
52 |
53 | interface State {
54 | toasts: ToasterToast[]
55 | }
56 |
57 | const toastTimeouts = new Map>()
58 |
59 | const addToRemoveQueue = (toastId: string) => {
60 | if (toastTimeouts.has(toastId)) {
61 | return
62 | }
63 |
64 | const timeout = setTimeout(() => {
65 | toastTimeouts.delete(toastId)
66 | dispatch({
67 | type: "REMOVE_TOAST",
68 | toastId: toastId,
69 | })
70 | }, TOAST_REMOVE_DELAY)
71 |
72 | toastTimeouts.set(toastId, timeout)
73 | }
74 |
75 | export const reducer = (state: State, action: Action): State => {
76 | switch (action.type) {
77 | case "ADD_TOAST":
78 | return {
79 | ...state,
80 | toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
81 | }
82 |
83 | case "UPDATE_TOAST":
84 | return {
85 | ...state,
86 | toasts: state.toasts.map((t) =>
87 | t.id === action.toast.id ? { ...t, ...action.toast } : t
88 | ),
89 | }
90 |
91 | case "DISMISS_TOAST": {
92 | const { toastId } = action
93 |
94 | // ! Side effects ! - This could be extracted into a dismissToast() action,
95 | // but I'll keep it here for simplicity
96 | if (toastId) {
97 | addToRemoveQueue(toastId)
98 | } else {
99 | state.toasts.forEach((toast) => {
100 | addToRemoveQueue(toast.id)
101 | })
102 | }
103 |
104 | return {
105 | ...state,
106 | toasts: state.toasts.map((t) =>
107 | t.id === toastId || toastId === undefined
108 | ? {
109 | ...t,
110 | open: false,
111 | }
112 | : t
113 | ),
114 | }
115 | }
116 | case "REMOVE_TOAST":
117 | if (action.toastId === undefined) {
118 | return {
119 | ...state,
120 | toasts: [],
121 | }
122 | }
123 | return {
124 | ...state,
125 | toasts: state.toasts.filter((t) => t.id !== action.toastId),
126 | }
127 | }
128 | }
129 |
130 | const listeners: Array<(state: State) => void> = []
131 |
132 | let memoryState: State = { toasts: [] }
133 |
134 | function dispatch(action: Action) {
135 | memoryState = reducer(memoryState, action)
136 | listeners.forEach((listener) => {
137 | listener(memoryState)
138 | })
139 | }
140 |
141 | type Toast = Omit
142 |
143 | function toast({ ...props }: Toast) {
144 | const id = genId()
145 |
146 | const update = (props: ToasterToast) =>
147 | dispatch({
148 | type: "UPDATE_TOAST",
149 | toast: { ...props, id },
150 | })
151 | const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
152 |
153 | dispatch({
154 | type: "ADD_TOAST",
155 | toast: {
156 | ...props,
157 | id,
158 | open: true,
159 | onOpenChange: (open) => {
160 | if (!open) dismiss()
161 | },
162 | },
163 | })
164 |
165 | return {
166 | id: id,
167 | dismiss,
168 | update,
169 | }
170 | }
171 |
172 | function useToast() {
173 | const [state, setState] = React.useState(memoryState)
174 |
175 | React.useEffect(() => {
176 | listeners.push(setState)
177 | return () => {
178 | const index = listeners.indexOf(setState)
179 | if (index > -1) {
180 | listeners.splice(index, 1)
181 | }
182 | }
183 | }, [state])
184 |
185 | return {
186 | ...state,
187 | toast,
188 | dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
189 | }
190 | }
191 |
192 | export { useToast, toast }
193 |
--------------------------------------------------------------------------------
/src/components/user-nav.tsx:
--------------------------------------------------------------------------------
1 | import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
2 | import { Button } from '@/components/custom/button'
3 | import {
4 | DropdownMenu,
5 | DropdownMenuContent,
6 | DropdownMenuGroup,
7 | DropdownMenuItem,
8 | DropdownMenuLabel,
9 | DropdownMenuSeparator,
10 | DropdownMenuShortcut,
11 | DropdownMenuTrigger,
12 | } from '@/components/ui/dropdown-menu'
13 | import { useNavigate } from 'react-router-dom'
14 |
15 | export function UserNav() {
16 | const navigate = useNavigate()
17 | const handleLogout = () => {
18 | sessionStorage.removeItem('token')
19 | navigate('/sign-in')
20 | }
21 | return (
22 |
23 |
24 |
30 |
31 |
32 |
33 |
34 | test
35 |
36 | test@gmail.com
37 |
38 |
39 |
40 |
41 |
42 |
43 | Profile
44 | ⇧⌘P
45 |
46 |
47 | Billing
48 | ⌘B
49 |
50 |
51 | Settings
52 | ⌘S
53 |
54 | New Team
55 |
56 |
57 |
58 | Log out
59 | ⇧⌘Q
60 |
61 |
62 |
63 | )
64 | }
65 |
--------------------------------------------------------------------------------
/src/config.tsx:
--------------------------------------------------------------------------------
1 | export const apiURL = 'http://localhost:3001/api/v1';
2 |
--------------------------------------------------------------------------------
/src/data/sidelinks.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | IconApps,
3 | IconBarrierBlock,
4 | IconBoxSeam,
5 | IconChartHistogram,
6 | IconChecklist,
7 | IconComponents,
8 | IconError404,
9 | IconExclamationCircle,
10 | IconHexagonNumber1,
11 | IconHexagonNumber2,
12 | IconHexagonNumber3,
13 | IconHexagonNumber4,
14 | IconHexagonNumber5,
15 | IconLayoutDashboard,
16 | IconMessages,
17 | IconRouteAltLeft,
18 | IconServerOff,
19 | IconSettings,
20 | IconTruck,
21 | IconUserShield,
22 | IconUsers,
23 | } from '@tabler/icons-react'
24 |
25 | export interface NavLink {
26 | title: string
27 | label?: string
28 | href: string
29 | icon: JSX.Element
30 | }
31 |
32 | export interface SideLink extends NavLink {
33 | sub?: NavLink[]
34 | }
35 |
36 | export const sidelinks: SideLink[] = [
37 | {
38 | title: 'Dashboard',
39 | label: '',
40 | href: '/dashboard',
41 | icon: ,
42 | },
43 | {
44 | title: 'Tasks',
45 | label: '3',
46 | href: '/tasks',
47 | icon: ,
48 | },
49 | {
50 | title: 'Chats',
51 | label: '9',
52 | href: '/chats',
53 | icon: ,
54 | },
55 | {
56 | title: 'Apps',
57 | label: '',
58 | href: '/apps',
59 | icon: ,
60 | },
61 | {
62 | title: 'Users',
63 | label: '',
64 | href: '/users',
65 | icon: ,
66 | },
67 | {
68 | title: 'Requests',
69 | label: '10',
70 | href: '/requests',
71 | icon: ,
72 | sub: [
73 | {
74 | title: 'Trucks',
75 | label: '9',
76 | href: '/trucks',
77 | icon: ,
78 | },
79 | {
80 | title: 'Cargos',
81 | label: '',
82 | href: '/cargos',
83 | icon: ,
84 | },
85 | ],
86 | },
87 | {
88 | title: 'Analysis',
89 | label: '',
90 | href: '/analysis',
91 | icon: ,
92 | },
93 | {
94 | title: 'Extra Components',
95 | label: '',
96 | href: '/extra-components',
97 | icon: ,
98 | },
99 | {
100 | title: 'Error Pages',
101 | label: '',
102 | href: '',
103 | icon: ,
104 | sub: [
105 | {
106 | title: 'Not Found',
107 | label: '',
108 | href: '/404',
109 | icon: ,
110 | },
111 | {
112 | title: 'Internal Server Error',
113 | label: '',
114 | href: '/500',
115 | icon: ,
116 | },
117 | {
118 | title: 'Maintenance Error',
119 | label: '',
120 | href: '/503',
121 | icon: ,
122 | },
123 | ],
124 | },
125 | {
126 | title: 'Settings',
127 | label: '',
128 | href: '/settings',
129 | icon: ,
130 | },
131 | ]
132 |
--------------------------------------------------------------------------------
/src/hooks/use-check-active-nav.tsx:
--------------------------------------------------------------------------------
1 | import { useLocation } from 'react-router-dom'
2 |
3 | export default function useCheckActiveNav() {
4 | const { pathname } = useLocation()
5 |
6 | const checkActiveNav = (nav: string) => {
7 | const pathArray = pathname.split('/').filter((item) => item !== '')
8 |
9 | if (nav === '/' && pathArray.length < 1) return true
10 |
11 | return pathArray.includes(nav.replace(/^\//, ''))
12 | }
13 |
14 | return { checkActiveNav }
15 | }
16 |
--------------------------------------------------------------------------------
/src/hooks/use-is-collapsed.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react'
2 | import useLocalStorage from './use-local-storage'
3 |
4 | export default function useIsCollapsed() {
5 | const [isCollapsed, setIsCollapsed] = useLocalStorage({
6 | key: 'collapsed-sidebar',
7 | defaultValue: false,
8 | })
9 |
10 | useEffect(() => {
11 | const handleResize = () => {
12 | // Update isCollapsed based on window.innerWidth
13 | setIsCollapsed(window.innerWidth < 768 ? false : isCollapsed)
14 | }
15 |
16 | // Initial setup
17 | handleResize()
18 |
19 | // Add event listener for window resize
20 | window.addEventListener('resize', handleResize)
21 |
22 | // Cleanup event listener on component unmount
23 | return () => {
24 | window.removeEventListener('resize', handleResize)
25 | }
26 | }, [isCollapsed, setIsCollapsed])
27 |
28 | return [isCollapsed, setIsCollapsed] as const
29 | }
30 |
--------------------------------------------------------------------------------
/src/hooks/use-local-storage.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react'
2 |
3 | interface LocalStorageProps {
4 | key: string
5 | defaultValue: T
6 | }
7 |
8 | export default function useLocalStorage({
9 | key,
10 | defaultValue,
11 | }: LocalStorageProps) {
12 | const [value, setValue] = useState(() => {
13 | const storedValue = localStorage.getItem(key)
14 | return storedValue !== null ? (JSON.parse(storedValue) as T) : defaultValue
15 | })
16 |
17 | useEffect(() => {
18 | localStorage.setItem(key, JSON.stringify(value))
19 | }, [value, key])
20 |
21 | return [value, setValue] as const
22 | }
23 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer base {
6 | :root {
7 | /* Custom CSS Variables */
8 | --header-height: 4rem;
9 |
10 | /* CSS Variables for light theme */
11 | --background: 0 0% 100%;
12 | --foreground: 222.2 84% 4.9%;
13 |
14 | --card: 0 0% 100%;
15 | --card-foreground: 222.2 84% 4.9%;
16 |
17 | --popover: 0 0% 100%;
18 | --popover-foreground: 222.2 84% 4.9%;
19 |
20 | --primary: 222.2 47.4% 11.2%;
21 | --primary-foreground: 210 40% 98%;
22 |
23 | --secondary: 210 40% 96.1%;
24 | --secondary-foreground: 222.2 47.4% 11.2%;
25 |
26 | --muted: 210 40% 96.1%;
27 | --muted-foreground: 215.4 16.3% 46.9%;
28 |
29 | --accent: 210 40% 96.1%;
30 | --accent-foreground: 222.2 47.4% 11.2%;
31 |
32 | --destructive: 0 84.2% 60.2%;
33 | --destructive-foreground: 210 40% 98%;
34 |
35 | --border: 214.3 31.8% 91.4%;
36 | --input: 214.3 31.8% 91.4%;
37 | --ring: 222.2 84% 4.9%;
38 |
39 | --radius: 0.5rem;
40 | }
41 |
42 | .dark {
43 | --background: 222.2 84% 4.9%;
44 | --foreground: 210 40% 98%;
45 |
46 | --card: 222.2 84% 4.9%;
47 | --card-foreground: 210 40% 98%;
48 |
49 | --popover: 222.2 84% 4.9%;
50 | --popover-foreground: 210 40% 98%;
51 |
52 | --primary: 210 40% 98%;
53 | --primary-foreground: 222.2 47.4% 11.2%;
54 |
55 | --secondary: 217.2 32.6% 17.5%;
56 | --secondary-foreground: 210 40% 98%;
57 |
58 | --muted: 217.2 32.6% 17.5%;
59 | --muted-foreground: 215 20.2% 65.1%;
60 |
61 | --accent: 217.2 32.6% 17.5%;
62 | --accent-foreground: 210 40% 98%;
63 |
64 | --destructive: 0 62.8% 30.6%;
65 | --destructive-foreground: 210 40% 98%;
66 |
67 | --border: 217.2 32.6% 17.5%;
68 | --input: 217.2 32.6% 17.5%;
69 | --ring: 212.7 26.8% 83.9%;
70 | }
71 |
72 | /* styles.css */
73 | .collapsibleDropdown {
74 | overflow: hidden;
75 | }
76 | .collapsibleDropdown[data-state='open'] {
77 | animation: slideDown 200ms ease-out;
78 | }
79 | .collapsibleDropdown[data-state='closed'] {
80 | animation: slideUp 200ms ease-out;
81 | }
82 |
83 | @keyframes slideDown {
84 | from {
85 | height: 0;
86 | }
87 | to {
88 | height: var(--radix-collapsible-content-height);
89 | }
90 | }
91 |
92 | @keyframes slideUp {
93 | from {
94 | height: var(--radix-collapsible-content-height);
95 | }
96 | to {
97 | height: 0;
98 | }
99 | }
100 |
101 | * {
102 | @apply border-border;
103 | }
104 | body {
105 | @apply min-h-svh w-full bg-background text-foreground;
106 | }
107 | }
108 |
109 | @layer utilities {
110 | /* Hide scrollbar for Chrome, Safari and Opera */
111 | .no-scrollbar::-webkit-scrollbar {
112 | display: none;
113 | }
114 | /* Hide scrollbar for IE, Edge and Firefox */
115 | .no-scrollbar {
116 | -ms-overflow-style: none; /* IE and Edge */
117 | scrollbar-width: none; /* Firefox */
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx"
2 | import { twMerge } from "tailwind-merge"
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs))
6 | }
7 |
--------------------------------------------------------------------------------
/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import { RouterProvider } from 'react-router-dom'
4 | import { Toaster } from '@/components/ui/toaster'
5 | import { ThemeProvider } from '@/components/theme-provider'
6 | import router from '@/router'
7 | import '@/index.css'
8 | import { Provider } from 'react-redux';
9 | import { store, persistor } from './redux/store'
10 | import { PersistGate } from 'redux-persist/integration/react';
11 |
12 | ReactDOM.createRoot(document.getElementById('root')!).render(
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | )
24 |
--------------------------------------------------------------------------------
/src/pages/apps/data.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | IconBrandDiscord,
3 | IconBrandDocker,
4 | IconBrandFigma,
5 | IconBrandGithub,
6 | IconBrandGitlab,
7 | IconBrandGmail,
8 | IconBrandMedium,
9 | IconBrandNotion,
10 | IconBrandSkype,
11 | IconBrandSlack,
12 | IconBrandStripe,
13 | IconBrandTelegram,
14 | IconBrandTrello,
15 | IconBrandWhatsapp,
16 | IconBrandZoom,
17 | } from '@tabler/icons-react'
18 |
19 | export const apps = [
20 | {
21 | name: 'Telegram',
22 | logo: ,
23 | connected: false,
24 | desc: 'Connect with Telegram for real-time communication.',
25 | },
26 | {
27 | name: 'Notion',
28 | logo: ,
29 | connected: true,
30 | desc: 'Effortlessly sync Notion pages for seamless collaboration.',
31 | },
32 | {
33 | name: 'Figma',
34 | logo: ,
35 | connected: true,
36 | desc: 'View and collaborate on Figma designs in one place.',
37 | },
38 | {
39 | name: 'Trello',
40 | logo: ,
41 | connected: false,
42 | desc: 'Sync Trello cards for streamlined project management.',
43 | },
44 | {
45 | name: 'Slack',
46 | logo: ,
47 | connected: false,
48 | desc: 'Integrate Slack for efficient team communication',
49 | },
50 | {
51 | name: 'Zoom',
52 | logo: ,
53 | connected: true,
54 | desc: 'Host Zoom meetings directly from the dashboard.',
55 | },
56 | {
57 | name: 'Stripe',
58 | logo: ,
59 | connected: false,
60 | desc: 'Easily manage Stripe transactions and payments.',
61 | },
62 | {
63 | name: 'Gmail',
64 | logo: ,
65 | connected: true,
66 | desc: 'Access and manage Gmail messages effortlessly.',
67 | },
68 | {
69 | name: 'Medium',
70 | logo: ,
71 | connected: false,
72 | desc: 'Explore and share Medium stories on your dashboard.',
73 | },
74 | {
75 | name: 'Skype',
76 | logo: ,
77 | connected: false,
78 | desc: 'Connect with Skype contacts seamlessly.',
79 | },
80 | {
81 | name: 'Docker',
82 | logo: ,
83 | connected: false,
84 | desc: 'Effortlessly manage Docker containers on your dashboard.',
85 | },
86 | {
87 | name: 'GitHub',
88 | logo: ,
89 | connected: false,
90 | desc: 'Streamline code management with GitHub integration.',
91 | },
92 | {
93 | name: 'GitLab',
94 | logo: ,
95 | connected: false,
96 | desc: 'Efficiently manage code projects with GitLab integration.',
97 | },
98 | {
99 | name: 'Discord',
100 | logo: ,
101 | connected: false,
102 | desc: 'Connect with Discord for seamless team communication.',
103 | },
104 | {
105 | name: 'WhatsApp',
106 | logo: ,
107 | connected: false,
108 | desc: 'Easily integrate WhatsApp for direct messaging.',
109 | },
110 | ]
111 |
--------------------------------------------------------------------------------
/src/pages/apps/index.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 | import {
3 | IconAdjustmentsHorizontal,
4 | IconSortAscendingLetters,
5 | IconSortDescendingLetters,
6 | } from '@tabler/icons-react'
7 | import { Layout, LayoutBody, LayoutHeader } from '@/components/custom/layout'
8 | import { Input } from '@/components/ui/input'
9 | import {
10 | Select,
11 | SelectContent,
12 | SelectItem,
13 | SelectTrigger,
14 | SelectValue,
15 | } from '@/components/ui/select'
16 | import { Separator } from '@/components/ui/separator'
17 | import { Search } from '@/components/search'
18 | import ThemeSwitch from '@/components/theme-switch'
19 | import { UserNav } from '@/components/user-nav'
20 | import { Button } from '@/components/custom/button'
21 | import { apps } from './data'
22 |
23 | const appText = new Map([
24 | ['all', 'All Apps'],
25 | ['connected', 'Connected'],
26 | ['notConnected', 'Not Connected'],
27 | ])
28 |
29 | export default function Apps() {
30 | const [sort, setSort] = useState('ascending')
31 | const [appType, setAppType] = useState('all')
32 | const [searchTerm, setSearchTerm] = useState('')
33 |
34 | const filteredApps = apps
35 | .sort((a, b) =>
36 | sort === 'ascending'
37 | ? a.name.localeCompare(b.name)
38 | : b.name.localeCompare(a.name)
39 | )
40 | .filter((app) =>
41 | appType === 'connected'
42 | ? app.connected
43 | : appType === 'notConnected'
44 | ? !app.connected
45 | : true
46 | )
47 | .filter((app) => app.name.toLowerCase().includes(searchTerm.toLowerCase()))
48 |
49 | return (
50 |
51 | {/* ===== Top Heading ===== */}
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | {/* ===== Content ===== */}
63 |
64 |
65 |
66 | App Integrations
67 |
68 |
69 | Here's a list of your apps for the integration!
70 |
71 |
72 |
73 |
74 | setSearchTerm(e.target.value)}
79 | />
80 |
90 |
91 |
92 |
113 |
114 |
115 |
116 | {filteredApps.map((app) => (
117 | -
121 |
122 |
125 | {app.logo}
126 |
127 |
134 |
135 |
136 | {app.name}
137 | {app.desc}
138 |
139 |
140 | ))}
141 |
142 |
143 |
144 | )
145 | }
146 |
--------------------------------------------------------------------------------
/src/pages/auth/components/forgot-form.tsx:
--------------------------------------------------------------------------------
1 | import { HTMLAttributes, useState } from 'react'
2 | import { cn } from '@/lib/utils'
3 | import { zodResolver } from '@hookform/resolvers/zod'
4 | import { useForm } from 'react-hook-form'
5 | import { z } from 'zod'
6 | import { Button } from '@/components/custom/button'
7 | import {
8 | Form,
9 | FormControl,
10 | FormField,
11 | FormItem,
12 | FormLabel,
13 | FormMessage,
14 | } from '@/components/ui/form'
15 | import { Input } from '@/components/ui/input'
16 |
17 | interface ForgotFormProps extends HTMLAttributes {}
18 |
19 | const formSchema = z.object({
20 | email: z
21 | .string()
22 | .min(1, { message: 'Please enter your email' })
23 | .email({ message: 'Invalid email address' }),
24 | })
25 |
26 | export function ForgotForm({ className, ...props }: ForgotFormProps) {
27 | const [isLoading, setIsLoading] = useState(false)
28 |
29 | const form = useForm>({
30 | resolver: zodResolver(formSchema),
31 | defaultValues: { email: '' },
32 | })
33 |
34 | function onSubmit(data: z.infer) {
35 | setIsLoading(true)
36 | console.log(data)
37 |
38 | setTimeout(() => {
39 | setIsLoading(false)
40 | }, 3000)
41 | }
42 |
43 | return (
44 |
68 | )
69 | }
70 |
--------------------------------------------------------------------------------
/src/pages/auth/components/otp-form.tsx:
--------------------------------------------------------------------------------
1 | import { HTMLAttributes, useState } from 'react'
2 | import { cn } from '@/lib/utils'
3 | import { zodResolver } from '@hookform/resolvers/zod'
4 | import { useForm } from 'react-hook-form'
5 | import { z } from 'zod'
6 | import { Button } from '@/components/custom/button'
7 | import {
8 | Form,
9 | FormControl,
10 | FormField,
11 | FormItem,
12 | FormMessage,
13 | } from '@/components/ui/form'
14 | import { Input } from '@/components/ui/input'
15 | import { PinInput, PinInputField } from '@/components/custom/pin-input'
16 | import { Separator } from '@/components/ui/separator'
17 |
18 | interface OtpFormProps extends HTMLAttributes {}
19 |
20 | const formSchema = z.object({
21 | otp: z.string().min(1, { message: 'Please enter your otp code.' }),
22 | })
23 |
24 | export function OtpForm({ className, ...props }: OtpFormProps) {
25 | const [isLoading, setIsLoading] = useState(false)
26 | const [disabledBtn, setDisabledBtn] = useState(true)
27 |
28 | const form = useForm>({
29 | resolver: zodResolver(formSchema),
30 | defaultValues: { otp: '' },
31 | })
32 |
33 | function onSubmit(data: z.infer) {
34 | setIsLoading(true)
35 | console.log({ data })
36 |
37 | setTimeout(() => {
38 | form.reset()
39 | setIsLoading(false)
40 | }, 2000)
41 | }
42 |
43 | return (
44 |
84 | )
85 | }
86 |
--------------------------------------------------------------------------------
/src/pages/auth/components/sign-up-form.tsx:
--------------------------------------------------------------------------------
1 | import { HTMLAttributes, useState } from 'react'
2 | import { useForm } from 'react-hook-form'
3 | import { zodResolver } from '@hookform/resolvers/zod'
4 | import { IconBrandFacebook, IconBrandGithub } from '@tabler/icons-react'
5 | import { z } from 'zod'
6 | import {
7 | Form,
8 | FormControl,
9 | FormField,
10 | FormItem,
11 | FormLabel,
12 | FormMessage,
13 | } from '@/components/ui/form'
14 | import { Input } from '@/components/ui/input'
15 | import { Button } from '@/components/custom/button'
16 | import { PasswordInput } from '@/components/custom/password-input'
17 | import { cn } from '@/lib/utils'
18 |
19 | interface SignUpFormProps extends HTMLAttributes {}
20 |
21 | const formSchema = z
22 | .object({
23 | email: z
24 | .string()
25 | .min(1, { message: 'Please enter your email' })
26 | .email({ message: 'Invalid email address' }),
27 | password: z
28 | .string()
29 | .min(1, {
30 | message: 'Please enter your password',
31 | })
32 | .min(7, {
33 | message: 'Password must be at least 7 characters long',
34 | }),
35 | confirmPassword: z.string(),
36 | })
37 | .refine((data) => data.password === data.confirmPassword, {
38 | message: "Passwords don't match.",
39 | path: ['confirmPassword'],
40 | })
41 |
42 | export function SignUpForm({ className, ...props }: SignUpFormProps) {
43 | const [isLoading, setIsLoading] = useState(false)
44 |
45 | const form = useForm>({
46 | resolver: zodResolver(formSchema),
47 | defaultValues: {
48 | email: '',
49 | password: '',
50 | confirmPassword: '',
51 | },
52 | })
53 |
54 | function onSubmit(data: z.infer) {
55 | setIsLoading(true)
56 | console.log(data)
57 |
58 | setTimeout(() => {
59 | setIsLoading(false)
60 | }, 3000)
61 | }
62 |
63 | return (
64 |
114 | )
115 | }
116 |
--------------------------------------------------------------------------------
/src/pages/auth/components/user-auth-form.tsx:
--------------------------------------------------------------------------------
1 | import { HTMLAttributes, useState } from 'react'
2 | import { useForm } from 'react-hook-form'
3 | import { Link } from 'react-router-dom'
4 | import { z } from 'zod'
5 | import { zodResolver } from '@hookform/resolvers/zod'
6 | import { IconBrandFacebook, IconBrandGithub } from '@tabler/icons-react'
7 | import {
8 | Form,
9 | FormControl,
10 | FormField,
11 | FormItem,
12 | FormLabel,
13 | FormMessage,
14 | } from '@/components/ui/form'
15 | import { Input } from '@/components/ui/input'
16 | import { Button } from '@/components/custom/button'
17 | import { PasswordInput } from '@/components/custom/password-input'
18 | import { cn } from '@/lib/utils'
19 | import { useNavigate } from 'react-router-dom'
20 | import { toast } from '@/components/ui/use-toast'
21 | import { _login } from '@/redux/actions/signIn'
22 | import { useDispatch } from 'react-redux'
23 |
24 | interface UserAuthFormProps extends HTMLAttributes {}
25 |
26 | const formSchema = z.object({
27 | email: z
28 | .string()
29 | .min(1, { message: 'Please enter your email' })
30 | .email({ message: 'Invalid email address' }),
31 | password: z
32 | .string()
33 | .min(1, {
34 | message: 'Please enter your password',
35 | })
36 | .min(7, {
37 | message: 'Password must be at least 7 characters long',
38 | }),
39 | })
40 |
41 | export function UserAuthForm({ className, ...props }: UserAuthFormProps) {
42 | const dispatch = useDispatch();
43 | const [isLoading, setIsLoading] = useState(false)
44 | const navigate = useNavigate();
45 | const form = useForm>({
46 | resolver: zodResolver(formSchema),
47 | defaultValues: {
48 | email: '',
49 | password: '',
50 | },
51 | })
52 |
53 | async function onSubmit(data: z.infer) {
54 | setIsLoading(true)
55 | const request = {...data}
56 | const {payload} = await dispatch(_login({request, navigate}))
57 | if(payload && payload?.data && payload?.data?.token) {
58 | setIsLoading(false)
59 | sessionStorage.setItem('token', payload?.data?.token)
60 | navigate('/dashboard')
61 | toast({title: 'Login Successful'})
62 | }
63 | else {
64 | setIsLoading(false)
65 | toast({variant: "destructive", title: payload?.data?.message || 'Login unsuccessful'})
66 | }
67 | }
68 |
69 | return (
70 |
116 | )
117 | }
118 |
--------------------------------------------------------------------------------
/src/pages/auth/forgot-password.tsx:
--------------------------------------------------------------------------------
1 | import { Card } from '@/components/ui/card'
2 | import { ForgotForm } from './components/forgot-form'
3 | import { Link } from 'react-router-dom'
4 |
5 | export default function ForgotPassword() {
6 | return (
7 | <>
8 |
9 |
10 |
11 |
23 | Shadcn Admin
24 |
25 |
26 |
27 |
28 | Forgot Password
29 |
30 |
31 | Enter your registered email and we will send you a link
32 | to reset your password.
33 |
34 |
35 |
36 |
37 | Don't have an account?{' '}
38 |
42 | Sign up
43 |
44 | .
45 |
46 |
47 |
48 |
49 | >
50 | )
51 | }
52 |
--------------------------------------------------------------------------------
/src/pages/auth/otp.tsx:
--------------------------------------------------------------------------------
1 | import { Card } from '@/components/ui/card'
2 | import { Link } from 'react-router-dom'
3 | import { OtpForm } from './components/otp-form'
4 |
5 | export default function Otp() {
6 | return (
7 | <>
8 |
9 |
10 |
11 |
23 | Shadcn Admin
24 |
25 |
26 |
27 |
28 | Two-factor Authentication
29 |
30 |
31 | Please enter the authentication code. We have sent the
32 | authentication code to your email.
33 |
34 |
35 |
36 |
37 | Haven't received it?{' '}
38 |
42 | Resend a new code.
43 |
44 | .
45 |
46 |
47 |
48 |
49 | >
50 | )
51 | }
52 |
--------------------------------------------------------------------------------
/src/pages/auth/sign-in-2.tsx:
--------------------------------------------------------------------------------
1 | import { Card } from '@/components/ui/card'
2 | import { UserAuthForm } from './components/user-auth-form'
3 |
4 | export default function SignIn2() {
5 | return (
6 | <>
7 |
8 |
9 |
10 |
22 | Shadcn Admin
23 |
24 |
25 |
26 | Login
27 |
28 | Enter your email and password below
29 | to log into your account
30 |
31 |
32 |
33 |
34 | By clicking login, you agree to our{' '}
35 |
39 | Terms of Service
40 | {' '}
41 | and{' '}
42 |
46 | Privacy Policy
47 |
48 | .
49 |
50 |
51 |
52 |
53 | >
54 | )
55 | }
56 |
--------------------------------------------------------------------------------
/src/pages/auth/sign-in.tsx:
--------------------------------------------------------------------------------
1 | import { UserAuthForm } from './components/user-auth-form'
2 | import ViteLogo from '@/assets/vite.svg'
3 |
4 | export default function SignIn() {
5 | return (
6 | <>
7 |
8 |
9 |
10 |
11 |
23 | Template
24 |
25 |
26 | 
33 |
34 |
35 |
36 |
37 |
38 | Login
39 |
40 | Enter your email and password below
41 | to log into your account
42 |
43 |
44 |
45 |
46 | By clicking login, you agree to our{' '}
47 |
51 | Terms of Service
52 | {' '}
53 | and{' '}
54 |
58 | Privacy Policy
59 |
60 | .
61 |
62 |
63 |
64 |
65 | >
66 | )
67 | }
68 |
--------------------------------------------------------------------------------
/src/pages/auth/sign-up.tsx:
--------------------------------------------------------------------------------
1 | import { Card } from '@/components/ui/card'
2 | import { SignUpForm } from './components/sign-up-form'
3 | import { Link } from 'react-router-dom'
4 |
5 | export default function SignUp() {
6 | return (
7 | <>
8 |
9 |
10 |
11 |
23 | Shadcn Admin
24 |
25 |
26 |
27 |
28 | Create an account
29 |
30 |
31 | Enter your email and password to create an account.
32 | Already have an account?{' '}
33 |
37 | Sign In
38 |
39 |
40 |
41 |
42 |
43 | By creating an account, you agree to our{' '}
44 |
48 | Terms of Service
49 | {' '}
50 | and{' '}
51 |
55 | Privacy Policy
56 |
57 | .
58 |
59 |
60 |
61 |
62 | >
63 | )
64 | }
65 |
--------------------------------------------------------------------------------
/src/pages/dashboard/components/overview.tsx:
--------------------------------------------------------------------------------
1 | import { Bar, BarChart, ResponsiveContainer, XAxis, YAxis } from 'recharts'
2 |
3 | const data = [
4 | {
5 | name: 'Jan',
6 | total: Math.floor(Math.random() * 5000) + 1000,
7 | },
8 | {
9 | name: 'Feb',
10 | total: Math.floor(Math.random() * 5000) + 1000,
11 | },
12 | {
13 | name: 'Mar',
14 | total: Math.floor(Math.random() * 5000) + 1000,
15 | },
16 | {
17 | name: 'Apr',
18 | total: Math.floor(Math.random() * 5000) + 1000,
19 | },
20 | {
21 | name: 'May',
22 | total: Math.floor(Math.random() * 5000) + 1000,
23 | },
24 | {
25 | name: 'Jun',
26 | total: Math.floor(Math.random() * 5000) + 1000,
27 | },
28 | {
29 | name: 'Jul',
30 | total: Math.floor(Math.random() * 5000) + 1000,
31 | },
32 | {
33 | name: 'Aug',
34 | total: Math.floor(Math.random() * 5000) + 1000,
35 | },
36 | {
37 | name: 'Sep',
38 | total: Math.floor(Math.random() * 5000) + 1000,
39 | },
40 | {
41 | name: 'Oct',
42 | total: Math.floor(Math.random() * 5000) + 1000,
43 | },
44 | {
45 | name: 'Nov',
46 | total: Math.floor(Math.random() * 5000) + 1000,
47 | },
48 | {
49 | name: 'Dec',
50 | total: Math.floor(Math.random() * 5000) + 1000,
51 | },
52 | ]
53 |
54 | export function Overview() {
55 | return (
56 |
57 |
58 |
65 | `$${value}`}
71 | />
72 |
78 |
79 |
80 | )
81 | }
82 |
--------------------------------------------------------------------------------
/src/pages/dashboard/components/recent-sales.tsx:
--------------------------------------------------------------------------------
1 | import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
2 |
3 | export function RecentSales() {
4 | return (
5 |
6 |
7 |
8 |
9 | OM
10 |
11 |
12 | Olivia Martin
13 |
14 | olivia.martin@email.com
15 |
16 |
17 | +$1,999.00
18 |
19 |
20 |
21 |
22 | JL
23 |
24 |
25 | Jackson Lee
26 | jackson.lee@email.com
27 |
28 | +$39.00
29 |
30 |
31 |
32 |
33 | IN
34 |
35 |
36 | Isabella Nguyen
37 |
38 | isabella.nguyen@email.com
39 |
40 |
41 | +$299.00
42 |
43 |
44 |
45 |
46 | WK
47 |
48 |
49 | William Kim
50 | will@email.com
51 |
52 | +$99.00
53 |
54 |
55 |
56 |
57 | SD
58 |
59 |
60 | Sofia Davis
61 | sofia.davis@email.com
62 |
63 | +$39.00
64 |
65 |
66 | )
67 | }
68 |
--------------------------------------------------------------------------------
/src/pages/errors/general-error.tsx:
--------------------------------------------------------------------------------
1 | import { useNavigate } from 'react-router-dom'
2 | import { Button } from '@/components/custom/button'
3 | import { cn } from '@/lib/utils'
4 |
5 | interface GeneralErrorProps extends React.HTMLAttributes {
6 | minimal?: boolean
7 | }
8 |
9 | export default function GeneralError({
10 | className,
11 | minimal = false,
12 | }: GeneralErrorProps) {
13 | const navigate = useNavigate()
14 | return (
15 |
16 |
17 | {!minimal && (
18 | 500
19 | )}
20 | Oops! Something went wrong {`:')`}
21 |
22 | We apologize for the inconvenience. Please try again later.
23 |
24 | {!minimal && (
25 |
26 |
29 |
30 |
31 | )}
32 |
33 |
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/src/pages/errors/maintenance-error.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from '@/components/custom/button'
2 |
3 | export default function MaintenanceError() {
4 | return (
5 |
6 |
7 | 503
8 | Website is under maintenance!
9 |
10 | The site is not available at the moment.
11 | We'll be back online shortly.
12 |
13 |
14 |
15 |
16 |
17 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/src/pages/errors/not-found-error.tsx:
--------------------------------------------------------------------------------
1 | import { useNavigate } from 'react-router-dom'
2 | import { Button } from '@/components/custom/button'
3 |
4 | export default function NotFoundError() {
5 | const navigate = useNavigate()
6 | return (
7 |
8 |
9 | 404
10 | Oops! Page Not Found!
11 |
12 | It seems like the page you're looking for
13 | does not exist or might have been removed.
14 |
15 |
16 |
19 |
20 |
21 |
22 |
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/src/pages/extra-components/index.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 | import { Link } from 'react-router-dom'
3 | import { IconChevronRight } from '@tabler/icons-react'
4 | import { nord } from 'react-syntax-highlighter/dist/esm/styles/prism'
5 | import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
6 | import { Layout, LayoutBody, LayoutHeader } from '@/components/custom/layout'
7 | import { Breadcrumb, BreadcrumbItem } from '@/components/custom/breadcrumb'
8 | import { PinInput, PinInputField } from '@/components/custom/pin-input'
9 | import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
10 | import { Separator } from '@/components/ui/separator'
11 | import { Input } from '@/components/ui/input'
12 | import { Search } from '@/components/search'
13 | import ThemeSwitch from '@/components/theme-switch'
14 | import { UserNav } from '@/components/user-nav'
15 |
16 | export default function ExtraComponents() {
17 | const items = [
18 | { title: 'Extra Components', href: '/extra-components' },
19 | { title: 'Breadcrumb' },
20 | ].map(({ href, title }) => (
21 |
22 | {href ? (
23 |
27 | {title}
28 |
29 | ) : (
30 | {title}
31 | )}
32 |
33 | ))
34 |
35 | const [pinInput, setPinInput] = useState('')
36 |
37 | return (
38 |
39 | {/* ===== Top Heading ===== */}
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | Extra Components
52 |
53 |
54 | Breadcrumbs
55 | }>
56 | {items}
57 |
58 | {items}
59 |
60 |
61 |
62 | Pin Input
63 |
64 |
65 | Uncontrolled
66 |
67 |
68 | Preview
69 | Code
70 |
71 |
72 |
73 | console.log('completed', str)}
76 | autoFocus
77 | >
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
93 | {`
97 | console.log('completed', str)
98 | }
99 | autoFocus
100 | >
101 |
102 |
103 |
104 |
105 |
106 |
107 | `}
108 |
109 |
110 |
111 |
112 |
113 | Controlled
114 |
115 |
116 | Preview
117 | Code
118 |
119 |
120 |
121 | console.log('completed', str)}
126 | >
127 | {Array.from({ length: 4 }, (_, i) => (
128 |
129 | ))}
130 |
131 |
132 |
133 |
134 |
140 | {`function ControlledPinInput() {
141 | const [pinInput, setPinInput] = useState('');
142 |
143 | return (
144 |
149 | console.log('completed', str)
150 | }
151 | >
152 | {Array.from({ length: 4 }, (_, i) => (
153 |
154 | ))}
155 |
156 | )
157 | }`}
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 | )
166 | }
167 |
--------------------------------------------------------------------------------
/src/pages/settings/account/index.tsx:
--------------------------------------------------------------------------------
1 | import { Separator } from '@/components/ui/separator'
2 | import { AccountForm } from './account-form'
3 |
4 | export default function SettingsAccount() {
5 | return (
6 |
7 |
8 | Account
9 |
10 | Update your account settings. Set your preferred language and
11 | timezone.
12 |
13 |
14 |
15 |
16 |
17 | )
18 | }
19 |
--------------------------------------------------------------------------------
/src/pages/settings/appearance/index.tsx:
--------------------------------------------------------------------------------
1 | import { AppearanceForm } from './appearance-form'
2 | import { Separator } from '@/components/ui/separator'
3 |
4 | export default function SettingsAppearance() {
5 | return (
6 |
7 |
8 | Appearance
9 |
10 | Customize the appearance of the app. Automatically switch between day
11 | and night themes.
12 |
13 |
14 |
15 |
16 |
17 | )
18 | }
19 |
--------------------------------------------------------------------------------
/src/pages/settings/components/sidebar-nav.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 | import { Link, useLocation, useNavigate } from 'react-router-dom'
3 | import { buttonVariants } from '@/components/custom/button'
4 | import {
5 | Select,
6 | SelectContent,
7 | SelectItem,
8 | SelectTrigger,
9 | SelectValue,
10 | } from '@/components/ui/select'
11 | import { cn } from '@/lib/utils'
12 |
13 | interface SidebarNavProps extends React.HTMLAttributes {
14 | items: {
15 | href: string
16 | title: string
17 | icon: JSX.Element
18 | }[]
19 | }
20 |
21 | export default function SidebarNav({
22 | className,
23 | items,
24 | ...props
25 | }: SidebarNavProps) {
26 | const { pathname } = useLocation()
27 | const navigate = useNavigate()
28 | const [val, setVal] = useState(pathname ?? '/settings')
29 |
30 | const handleSelect = (e: string) => {
31 | setVal(e)
32 | navigate(e)
33 | }
34 |
35 | return (
36 | <>
37 |
38 |
53 |
54 |
55 |
56 |
80 |
81 | >
82 | )
83 | }
84 |
--------------------------------------------------------------------------------
/src/pages/settings/display/display-form.tsx:
--------------------------------------------------------------------------------
1 | import { zodResolver } from '@hookform/resolvers/zod'
2 | import { useForm } from 'react-hook-form'
3 | import { z } from 'zod'
4 |
5 | import { Button } from '@/components/custom/button'
6 | import { Checkbox } from '@/components/ui/checkbox'
7 | import {
8 | Form,
9 | FormControl,
10 | FormDescription,
11 | FormField,
12 | FormItem,
13 | FormLabel,
14 | FormMessage,
15 | } from '@/components/ui/form'
16 | import { toast } from '@/components/ui/use-toast'
17 |
18 | const items = [
19 | {
20 | id: 'recents',
21 | label: 'Recents',
22 | },
23 | {
24 | id: 'home',
25 | label: 'Home',
26 | },
27 | {
28 | id: 'applications',
29 | label: 'Applications',
30 | },
31 | {
32 | id: 'desktop',
33 | label: 'Desktop',
34 | },
35 | {
36 | id: 'downloads',
37 | label: 'Downloads',
38 | },
39 | {
40 | id: 'documents',
41 | label: 'Documents',
42 | },
43 | ] as const
44 |
45 | const displayFormSchema = z.object({
46 | items: z.array(z.string()).refine((value) => value.some((item) => item), {
47 | message: 'You have to select at least one item.',
48 | }),
49 | })
50 |
51 | type DisplayFormValues = z.infer
52 |
53 | // This can come from your database or API.
54 | const defaultValues: Partial = {
55 | items: ['recents', 'home'],
56 | }
57 |
58 | export function DisplayForm() {
59 | const form = useForm({
60 | resolver: zodResolver(displayFormSchema),
61 | defaultValues,
62 | })
63 |
64 | function onSubmit(data: DisplayFormValues) {
65 | toast({
66 | title: 'You submitted the following values:',
67 | description: (
68 |
69 | {JSON.stringify(data, null, 2)}
70 |
71 | ),
72 | })
73 | }
74 |
75 | return (
76 |
128 |
129 | )
130 | }
131 |
--------------------------------------------------------------------------------
/src/pages/settings/display/index.tsx:
--------------------------------------------------------------------------------
1 | import { Separator } from '@/components/ui/separator'
2 | import { DisplayForm } from './display-form'
3 |
4 | export default function SettingsDisplay() {
5 | return (
6 |
7 |
8 | Display
9 |
10 | Turn items on or off to control what's displayed in the app.
11 |
12 |
13 |
14 |
15 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/src/pages/settings/error-example/index.tsx:
--------------------------------------------------------------------------------
1 | export default function ErrorExample() {
2 | throw Error('an error occurs while loading this component')
3 | return Error Example
4 | }
5 |
--------------------------------------------------------------------------------
/src/pages/settings/index.tsx:
--------------------------------------------------------------------------------
1 | import { Outlet } from 'react-router-dom'
2 | import {
3 | IconBrowserCheck,
4 | IconExclamationCircle,
5 | IconNotification,
6 | IconPalette,
7 | IconTool,
8 | IconUser,
9 | } from '@tabler/icons-react'
10 | import { Search } from '@/components/search'
11 | import { Separator } from '@/components/ui/separator'
12 | import ThemeSwitch from '@/components/theme-switch'
13 | import { UserNav } from '@/components/user-nav'
14 | import { Layout, LayoutBody, LayoutHeader } from '@/components/custom/layout'
15 | import SidebarNav from './components/sidebar-nav'
16 |
17 | export default function Settings() {
18 | return (
19 |
20 | {/* ===== Top Heading ===== */}
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | Settings
33 |
34 |
35 | Manage your account settings and set e-mail preferences.
36 |
37 |
38 |
39 |
49 |
50 |
51 | )
52 | }
53 |
54 | const sidebarNavItems = [
55 | {
56 | title: 'Profile',
57 | icon: ,
58 | href: '/settings',
59 | },
60 | {
61 | title: 'Account',
62 | icon: ,
63 | href: '/settings/account',
64 | },
65 | {
66 | title: 'Appearance',
67 | icon: ,
68 | href: '/settings/appearance',
69 | },
70 | {
71 | title: 'Notifications',
72 | icon: ,
73 | href: '/settings/notifications',
74 | },
75 | {
76 | title: 'Display',
77 | icon: ,
78 | href: '/settings/display',
79 | },
80 | {
81 | title: 'Error Example',
82 | icon: ,
83 | href: '/settings/error-example',
84 | },
85 | ]
86 |
--------------------------------------------------------------------------------
/src/pages/settings/notifications/index.tsx:
--------------------------------------------------------------------------------
1 | import { Separator } from '@/components/ui/separator'
2 | import { NotificationsForm } from './notifications-form'
3 |
4 | export default function SettingsNotifications() {
5 | return (
6 |
7 |
8 | Notifications
9 |
10 | Configure how you receive notifications.
11 |
12 |
13 |
14 |
15 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/src/pages/settings/profile/index.tsx:
--------------------------------------------------------------------------------
1 | import { Separator } from '@/components/ui/separator'
2 | import ProfileForm from './profile-form'
3 |
4 | export default function SettingsProfile() {
5 | return (
6 |
7 |
8 | Profile
9 |
10 | This is how others will see you on the site.
11 |
12 |
13 |
14 |
15 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/src/pages/settings/profile/profile-form.tsx:
--------------------------------------------------------------------------------
1 | import { z } from 'zod'
2 | import { Link } from 'react-router-dom'
3 | import { useFieldArray, useForm } from 'react-hook-form'
4 | import { Button } from '@/components/custom/button'
5 | import {
6 | Form,
7 | FormControl,
8 | FormDescription,
9 | FormField,
10 | FormItem,
11 | FormLabel,
12 | FormMessage,
13 | } from '@/components/ui/form'
14 | import { Input } from '@/components/ui/input'
15 | import {
16 | Select,
17 | SelectContent,
18 | SelectItem,
19 | SelectTrigger,
20 | SelectValue,
21 | } from '@/components/ui/select'
22 | import { Textarea } from '@/components/ui/textarea'
23 | import { toast } from '@/components/ui/use-toast'
24 | import { cn } from '@/lib/utils'
25 | import { zodResolver } from '@hookform/resolvers/zod'
26 |
27 | const profileFormSchema = z.object({
28 | username: z
29 | .string()
30 | .min(2, {
31 | message: 'Username must be at least 2 characters.',
32 | })
33 | .max(30, {
34 | message: 'Username must not be longer than 30 characters.',
35 | }),
36 | email: z
37 | .string({
38 | required_error: 'Please select an email to display.',
39 | })
40 | .email(),
41 | bio: z.string().max(160).min(4),
42 | urls: z
43 | .array(
44 | z.object({
45 | value: z.string().url({ message: 'Please enter a valid URL.' }),
46 | })
47 | )
48 | .optional(),
49 | })
50 |
51 | type ProfileFormValues = z.infer
52 |
53 | // This can come from your database or API.
54 | const defaultValues: Partial = {
55 | bio: 'I own a computer.',
56 | urls: [
57 | { value: '' },
58 | { value: '' },
59 | ],
60 | }
61 |
62 | export default function ProfileForm() {
63 | const form = useForm({
64 | resolver: zodResolver(profileFormSchema),
65 | defaultValues,
66 | mode: 'onChange',
67 | })
68 |
69 | const { fields, append } = useFieldArray({
70 | name: 'urls',
71 | control: form.control,
72 | })
73 |
74 | function onSubmit(data: ProfileFormValues) {
75 | toast({
76 | title: 'You submitted the following values:',
77 | description: (
78 |
79 | {JSON.stringify(data, null, 2)}
80 |
81 | ),
82 | })
83 | }
84 |
85 | return (
86 |
186 |
187 | )
188 | }
189 |
--------------------------------------------------------------------------------
/src/pages/tasks/components/columns.tsx:
--------------------------------------------------------------------------------
1 | import { ColumnDef } from '@tanstack/react-table'
2 |
3 | import { Badge } from '@/components/ui/badge'
4 | import { Checkbox } from '@/components/ui/checkbox'
5 | import { DataTableColumnHeader } from './data-table-column-header'
6 | import { DataTableRowActions } from './data-table-row-actions'
7 |
8 | import { labels, priorities, statuses } from '../data/data'
9 | import { Task } from '../data/schema'
10 |
11 | export const columns: ColumnDef[] = [
12 | {
13 | id: 'select',
14 | header: ({ table }) => (
15 | table.toggleAllPageRowsSelected(!!value)}
21 | aria-label='Select all'
22 | className='translate-y-[2px]'
23 | />
24 | ),
25 | cell: ({ row }) => (
26 | row.toggleSelected(!!value)}
29 | aria-label='Select row'
30 | className='translate-y-[2px]'
31 | />
32 | ),
33 | enableSorting: false,
34 | enableHiding: false,
35 | },
36 | {
37 | accessorKey: 'id',
38 | header: ({ column }) => (
39 |
40 | ),
41 | cell: ({ row }) => {row.getValue('id')} ,
42 | enableSorting: false,
43 | enableHiding: false,
44 | },
45 | {
46 | accessorKey: 'title',
47 | header: ({ column }) => (
48 |
49 | ),
50 | cell: ({ row }) => {
51 | const label = labels.find((label) => label.value === row.original.label)
52 |
53 | return (
54 |
55 | {label && {label.label}}
56 |
57 | {row.getValue('title')}
58 |
59 |
60 | )
61 | },
62 | },
63 | {
64 | accessorKey: 'status',
65 | header: ({ column }) => (
66 |
67 | ),
68 | cell: ({ row }) => {
69 | const status = statuses.find(
70 | (status) => status.value === row.getValue('status')
71 | )
72 |
73 | if (!status) {
74 | return null
75 | }
76 |
77 | return (
78 |
79 | {status.icon && (
80 |
81 | )}
82 | {status.label}
83 |
84 | )
85 | },
86 | filterFn: (row, id, value) => {
87 | return value.includes(row.getValue(id))
88 | },
89 | },
90 | {
91 | accessorKey: 'priority',
92 | header: ({ column }) => (
93 |
94 | ),
95 | cell: ({ row }) => {
96 | const priority = priorities.find(
97 | (priority) => priority.value === row.getValue('priority')
98 | )
99 |
100 | if (!priority) {
101 | return null
102 | }
103 |
104 | return (
105 |
106 | {priority.icon && (
107 |
108 | )}
109 | {priority.label}
110 |
111 | )
112 | },
113 | filterFn: (row, id, value) => {
114 | return value.includes(row.getValue(id))
115 | },
116 | },
117 | {
118 | id: 'actions',
119 | cell: ({ row }) => ,
120 | },
121 | ]
122 |
--------------------------------------------------------------------------------
/src/pages/tasks/components/data-table-column-header.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | ArrowDownIcon,
3 | ArrowUpIcon,
4 | CaretSortIcon,
5 | EyeNoneIcon,
6 | } from '@radix-ui/react-icons'
7 | import { Column } from '@tanstack/react-table'
8 |
9 | import { Button } from '@/components/custom/button'
10 | import {
11 | DropdownMenu,
12 | DropdownMenuContent,
13 | DropdownMenuItem,
14 | DropdownMenuSeparator,
15 | DropdownMenuTrigger,
16 | } from '@/components/ui/dropdown-menu'
17 | import { cn } from '@/lib/utils'
18 |
19 | interface DataTableColumnHeaderProps
20 | extends React.HTMLAttributes {
21 | column: Column
22 | title: string
23 | }
24 |
25 | export function DataTableColumnHeader({
26 | column,
27 | title,
28 | className,
29 | }: DataTableColumnHeaderProps) {
30 | if (!column.getCanSort()) {
31 | return {title}
32 | }
33 |
34 | return (
35 |
36 |
37 |
38 |
52 |
53 |
54 | column.toggleSorting(false)}>
55 |
56 | Asc
57 |
58 | column.toggleSorting(true)}>
59 |
60 | Desc
61 |
62 |
63 | column.toggleVisibility(false)}>
64 |
65 | Hide
66 |
67 |
68 |
69 |
70 | )
71 | }
72 |
--------------------------------------------------------------------------------
/src/pages/tasks/components/data-table-faceted-filter.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { CheckIcon, PlusCircledIcon } from '@radix-ui/react-icons'
3 | import { Column } from '@tanstack/react-table'
4 |
5 | import { cn } from '@/lib/utils'
6 | import { Badge } from '@/components/ui/badge'
7 | import { Button } from '@/components/custom/button'
8 | import {
9 | Command,
10 | CommandEmpty,
11 | CommandGroup,
12 | CommandInput,
13 | CommandItem,
14 | CommandList,
15 | CommandSeparator,
16 | } from '@/components/ui/command'
17 | import {
18 | Popover,
19 | PopoverContent,
20 | PopoverTrigger,
21 | } from '@/components/ui/popover'
22 | import { Separator } from '@/components/ui/separator'
23 |
24 | interface DataTableFacetedFilterProps {
25 | column?: Column
26 | title?: string
27 | options: {
28 | label: string
29 | value: string
30 | icon?: React.ComponentType<{ className?: string }>
31 | }[]
32 | }
33 |
34 | export function DataTableFacetedFilter({
35 | column,
36 | title,
37 | options,
38 | }: DataTableFacetedFilterProps) {
39 | const facets = column?.getFacetedUniqueValues()
40 | const selectedValues = new Set(column?.getFilterValue() as string[])
41 |
42 | return (
43 |
44 |
45 |
82 |
83 |
84 |
85 |
86 |
87 | No results found.
88 |
89 | {options.map((option) => {
90 | const isSelected = selectedValues.has(option.value)
91 | return (
92 | {
95 | if (isSelected) {
96 | selectedValues.delete(option.value)
97 | } else {
98 | selectedValues.add(option.value)
99 | }
100 | const filterValues = Array.from(selectedValues)
101 | column?.setFilterValue(
102 | filterValues.length ? filterValues : undefined
103 | )
104 | }}
105 | >
106 |
114 |
115 |
116 | {option.icon && (
117 |
118 | )}
119 | {option.label}
120 | {facets?.get(option.value) && (
121 |
122 | {facets.get(option.value)}
123 |
124 | )}
125 |
126 | )
127 | })}
128 |
129 | {selectedValues.size > 0 && (
130 | <>
131 |
132 |
133 | column?.setFilterValue(undefined)}
135 | className='justify-center text-center'
136 | >
137 | Clear filters
138 |
139 |
140 | >
141 | )}
142 |
143 |
144 |
145 |
146 | )
147 | }
148 |
--------------------------------------------------------------------------------
/src/pages/tasks/components/data-table-pagination.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | ChevronLeftIcon,
3 | ChevronRightIcon,
4 | DoubleArrowLeftIcon,
5 | DoubleArrowRightIcon,
6 | } from '@radix-ui/react-icons'
7 | import { Table } from '@tanstack/react-table'
8 |
9 | import { Button } from '@/components/custom/button'
10 | import {
11 | Select,
12 | SelectContent,
13 | SelectItem,
14 | SelectTrigger,
15 | SelectValue,
16 | } from '@/components/ui/select'
17 |
18 | interface DataTablePaginationProps {
19 | table: Table
20 | }
21 |
22 | export function DataTablePagination({
23 | table,
24 | }: DataTablePaginationProps) {
25 | return (
26 |
27 |
28 | {table.getFilteredSelectedRowModel().rows.length} of{' '}
29 | {table.getFilteredRowModel().rows.length} row(s) selected.
30 |
31 |
32 |
33 | Rows per page
34 |
51 |
52 |
53 | Page {table.getState().pagination.pageIndex + 1} of{' '}
54 | {table.getPageCount()}
55 |
56 |
57 |
66 |
75 |
84 |
93 |
94 |
95 |
96 | )
97 | }
98 |
--------------------------------------------------------------------------------
/src/pages/tasks/components/data-table-row-actions.tsx:
--------------------------------------------------------------------------------
1 | import { DotsHorizontalIcon } from '@radix-ui/react-icons'
2 | import { Row } from '@tanstack/react-table'
3 |
4 | import { Button } from '@/components/custom/button'
5 | import {
6 | DropdownMenu,
7 | DropdownMenuContent,
8 | DropdownMenuItem,
9 | DropdownMenuRadioGroup,
10 | DropdownMenuRadioItem,
11 | DropdownMenuSeparator,
12 | DropdownMenuShortcut,
13 | DropdownMenuSub,
14 | DropdownMenuSubContent,
15 | DropdownMenuSubTrigger,
16 | DropdownMenuTrigger,
17 | } from '@/components/ui/dropdown-menu'
18 |
19 | import { labels } from '../data/data'
20 | import { taskSchema } from '../data/schema'
21 |
22 | interface DataTableRowActionsProps {
23 | row: Row
24 | }
25 |
26 | export function DataTableRowActions({
27 | row,
28 | }: DataTableRowActionsProps) {
29 | const task = taskSchema.parse(row.original)
30 |
31 | return (
32 |
33 |
34 |
41 |
42 |
43 | Edit
44 | Make a copy
45 | Favorite
46 |
47 |
48 | Labels
49 |
50 |
51 | {labels.map((label) => (
52 |
53 | {label.label}
54 |
55 | ))}
56 |
57 |
58 |
59 |
60 |
61 | Delete
62 | ⌘⌫
63 |
64 |
65 |
66 | )
67 | }
68 |
--------------------------------------------------------------------------------
/src/pages/tasks/components/data-table-toolbar.tsx:
--------------------------------------------------------------------------------
1 | import { Cross2Icon } from '@radix-ui/react-icons'
2 | import { Table } from '@tanstack/react-table'
3 |
4 | import { Button } from '@/components/custom/button'
5 | import { Input } from '@/components/ui/input'
6 | import { DataTableViewOptions } from '../components/data-table-view-options'
7 |
8 | import { priorities, statuses } from '../data/data'
9 | import { DataTableFacetedFilter } from './data-table-faceted-filter'
10 |
11 | interface DataTableToolbarProps {
12 | table: Table
13 | }
14 |
15 | export function DataTableToolbar({
16 | table,
17 | }: DataTableToolbarProps) {
18 | const isFiltered = table.getState().columnFilters.length > 0
19 |
20 | return (
21 |
22 |
23 |
27 | table.getColumn('title')?.setFilterValue(event.target.value)
28 | }
29 | className='h-8 w-[150px] lg:w-[250px]'
30 | />
31 |
32 | {table.getColumn('status') && (
33 |
38 | )}
39 | {table.getColumn('priority') && (
40 |
45 | )}
46 |
47 | {isFiltered && (
48 |
56 | )}
57 |
58 |
59 |
60 | )
61 | }
62 |
--------------------------------------------------------------------------------
/src/pages/tasks/components/data-table-view-options.tsx:
--------------------------------------------------------------------------------
1 | import { DropdownMenuTrigger } from '@radix-ui/react-dropdown-menu'
2 | import { MixerHorizontalIcon } from '@radix-ui/react-icons'
3 | import { Table } from '@tanstack/react-table'
4 |
5 | import { Button } from '@/components/custom/button'
6 | import {
7 | DropdownMenu,
8 | DropdownMenuCheckboxItem,
9 | DropdownMenuContent,
10 | DropdownMenuLabel,
11 | DropdownMenuSeparator,
12 | } from '@/components/ui/dropdown-menu'
13 |
14 | interface DataTableViewOptionsProps {
15 | table: Table
16 | }
17 |
18 | export function DataTableViewOptions({
19 | table,
20 | }: DataTableViewOptionsProps) {
21 | return (
22 |
23 |
24 |
32 |
33 |
34 | Toggle columns
35 |
36 | {table
37 | .getAllColumns()
38 | .filter(
39 | (column) =>
40 | typeof column.accessorFn !== 'undefined' && column.getCanHide()
41 | )
42 | .map((column) => {
43 | return (
44 | column.toggleVisibility(!!value)}
49 | >
50 | {column.id}
51 |
52 | )
53 | })}
54 |
55 |
56 | )
57 | }
58 |
--------------------------------------------------------------------------------
/src/pages/tasks/components/data-table.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import {
3 | ColumnDef,
4 | ColumnFiltersState,
5 | SortingState,
6 | VisibilityState,
7 | flexRender,
8 | getCoreRowModel,
9 | getFacetedRowModel,
10 | getFacetedUniqueValues,
11 | getFilteredRowModel,
12 | getPaginationRowModel,
13 | getSortedRowModel,
14 | useReactTable,
15 | } from '@tanstack/react-table'
16 |
17 | import {
18 | Table,
19 | TableBody,
20 | TableCell,
21 | TableHead,
22 | TableHeader,
23 | TableRow,
24 | } from '@/components/ui/table'
25 |
26 | import { DataTablePagination } from '../components/data-table-pagination'
27 | import { DataTableToolbar } from '../components/data-table-toolbar'
28 |
29 | interface DataTableProps {
30 | columns: ColumnDef[]
31 | data: TData[]
32 | }
33 |
34 | export function DataTable({
35 | columns,
36 | data,
37 | }: DataTableProps) {
38 | const [rowSelection, setRowSelection] = React.useState({})
39 | const [columnVisibility, setColumnVisibility] =
40 | React.useState({})
41 | const [columnFilters, setColumnFilters] = React.useState(
42 | []
43 | )
44 | const [sorting, setSorting] = React.useState([])
45 |
46 | const table = useReactTable({
47 | data,
48 | columns,
49 | state: {
50 | sorting,
51 | columnVisibility,
52 | rowSelection,
53 | columnFilters,
54 | },
55 | enableRowSelection: true,
56 | onRowSelectionChange: setRowSelection,
57 | onSortingChange: setSorting,
58 | onColumnFiltersChange: setColumnFilters,
59 | onColumnVisibilityChange: setColumnVisibility,
60 | getCoreRowModel: getCoreRowModel(),
61 | getFilteredRowModel: getFilteredRowModel(),
62 | getPaginationRowModel: getPaginationRowModel(),
63 | getSortedRowModel: getSortedRowModel(),
64 | getFacetedRowModel: getFacetedRowModel(),
65 | getFacetedUniqueValues: getFacetedUniqueValues(),
66 | })
67 |
68 | return (
69 |
70 |
71 |
72 |
73 |
74 | {table.getHeaderGroups().map((headerGroup) => (
75 |
76 | {headerGroup.headers.map((header) => {
77 | return (
78 |
79 | {header.isPlaceholder
80 | ? null
81 | : flexRender(
82 | header.column.columnDef.header,
83 | header.getContext()
84 | )}
85 |
86 | )
87 | })}
88 |
89 | ))}
90 |
91 |
92 | {table.getRowModel().rows?.length ? (
93 | table.getRowModel().rows.map((row) => (
94 |
98 | {row.getVisibleCells().map((cell) => (
99 |
100 | {flexRender(
101 | cell.column.columnDef.cell,
102 | cell.getContext()
103 | )}
104 |
105 | ))}
106 |
107 | ))
108 | ) : (
109 |
110 |
114 | No results.
115 |
116 |
117 | )}
118 |
119 |
120 |
121 |
122 |
123 | )
124 | }
125 |
--------------------------------------------------------------------------------
/src/pages/tasks/data/data.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | ArrowDownIcon,
3 | ArrowRightIcon,
4 | ArrowUpIcon,
5 | CheckCircledIcon,
6 | CircleIcon,
7 | CrossCircledIcon,
8 | QuestionMarkCircledIcon,
9 | StopwatchIcon,
10 | } from '@radix-ui/react-icons'
11 |
12 | export const labels = [
13 | {
14 | value: 'bug',
15 | label: 'Bug',
16 | },
17 | {
18 | value: 'feature',
19 | label: 'Feature',
20 | },
21 | {
22 | value: 'documentation',
23 | label: 'Documentation',
24 | },
25 | ]
26 |
27 | export const statuses = [
28 | {
29 | value: 'backlog',
30 | label: 'Backlog',
31 | icon: QuestionMarkCircledIcon,
32 | },
33 | {
34 | value: 'todo',
35 | label: 'Todo',
36 | icon: CircleIcon,
37 | },
38 | {
39 | value: 'in progress',
40 | label: 'In Progress',
41 | icon: StopwatchIcon,
42 | },
43 | {
44 | value: 'done',
45 | label: 'Done',
46 | icon: CheckCircledIcon,
47 | },
48 | {
49 | value: 'canceled',
50 | label: 'Canceled',
51 | icon: CrossCircledIcon,
52 | },
53 | ]
54 |
55 | export const priorities = [
56 | {
57 | label: 'Low',
58 | value: 'low',
59 | icon: ArrowDownIcon,
60 | },
61 | {
62 | label: 'Medium',
63 | value: 'medium',
64 | icon: ArrowRightIcon,
65 | },
66 | {
67 | label: 'High',
68 | value: 'high',
69 | icon: ArrowUpIcon,
70 | },
71 | ]
72 |
--------------------------------------------------------------------------------
/src/pages/tasks/data/schema.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod'
2 |
3 | // We're keeping a simple non-relational schema here.
4 | // IRL, you will have a schema for your data models.
5 | export const taskSchema = z.object({
6 | id: z.string(),
7 | title: z.string(),
8 | status: z.string(),
9 | label: z.string(),
10 | priority: z.string(),
11 | })
12 |
13 | export type Task = z.infer
14 |
--------------------------------------------------------------------------------
/src/pages/tasks/index.tsx:
--------------------------------------------------------------------------------
1 | import { Search } from '@/components/search'
2 | import ThemeSwitch from '@/components/theme-switch'
3 | import { UserNav } from '@/components/user-nav'
4 | import { Layout, LayoutBody, LayoutHeader } from '@/components/custom/layout'
5 | import { DataTable } from './components/data-table'
6 | import { columns } from './components/columns'
7 | import { tasks } from './data/tasks'
8 |
9 | export default function Tasks() {
10 | return (
11 |
12 | {/* ===== Top Heading ===== */}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | Welcome back!
25 |
26 | Here's a list of your tasks for this month!
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | )
36 | }
37 |
--------------------------------------------------------------------------------
/src/redux/actions/signIn.tsx:
--------------------------------------------------------------------------------
1 | //For reference
2 | import { createAsyncThunk } from '@reduxjs/toolkit'
3 | import axios from 'axios';
4 | import { apiURL } from '@/config';
5 | import { toast } from '@/components/ui/use-toast'
6 |
7 | export const _login = createAsyncThunk('user/_login', async (data: any) => {
8 | try {
9 | const response = await axios(
10 | {
11 | method: 'POST',
12 | url: apiURL + '/user/login',
13 | data: data.request,
14 | headers: { Authorization: 'Bearer ' + sessionStorage.getItem('token') },
15 | })
16 | return response
17 | } catch (error: any) {
18 | const err = error;
19 | if (err.response) {
20 |
21 | if (err.response.status < 200 || err.response.status >= 300) {
22 | if (err.response.status === 401 || err.response.status === 402) {
23 | data.history('/LoginScreens/default-login');
24 | toast({
25 | variant: "destructive",
26 | title: "Please Login Again",
27 | });
28 | } else if (err.response.status === 422) {
29 | toast({
30 | variant: "destructive",
31 | title: err.response?.data?.error || 'Please Try Again',
32 | });
33 | } else {
34 | toast({
35 | variant: "destructive",
36 | title: 'Please Try Again',
37 | });
38 | }
39 | }
40 | } else {
41 | toast({
42 | variant: "destructive",
43 | title: 'Please Check Your Network',
44 | });
45 | }
46 | return error.response;
47 | }
48 | })
--------------------------------------------------------------------------------
/src/redux/reduces/index.tsx:
--------------------------------------------------------------------------------
1 | import { UnknownAction, combineReducers } from 'redux';
2 | import signinSlice from './signIn';
3 |
4 | const reducers = combineReducers({
5 | signin: signinSlice,
6 | });
7 |
8 | const rootReducer = (state: { signin: { isloading: boolean; }; } | Partial<{ signin: { isloading: boolean; } | undefined; }> | undefined, action: UnknownAction) => {
9 | if (action.type === 'user/_logout/fulfilled' || sessionStorage.getItem('token') === null) {
10 | return reducers(undefined, action);
11 | }
12 |
13 | return reducers(state, action);
14 | };
15 |
16 | export default rootReducer;
17 |
--------------------------------------------------------------------------------
/src/redux/reduces/signIn.tsx:
--------------------------------------------------------------------------------
1 | import { createSlice } from '@reduxjs/toolkit'
2 | import { _login } from '../actions/signIn'
3 |
4 | const signinSlice = createSlice({
5 | name: 'user',
6 | initialState: { isloading: false },
7 | reducers: {},
8 | extraReducers: (builder) => {
9 | builder
10 | .addCase(_login.pending, (state, action) => (
11 | {
12 | ...state,
13 | isloading: true,
14 | }))
15 | .addCase(_login.fulfilled, (state, action) => (
16 | {
17 | ...state,
18 | isloading: false,
19 | ...action.payload,
20 | }))
21 | .addCase(_login.rejected, (state, action) => (
22 | {
23 | ...state,
24 | isloading: false,
25 | ...action.error,
26 | }))
27 | },
28 | })
29 |
30 | export default signinSlice.reducer
--------------------------------------------------------------------------------
/src/redux/store.tsx:
--------------------------------------------------------------------------------
1 | import {configureStore} from '@reduxjs/toolkit';
2 | import rootReducer from './reduces';
3 | import { persistStore, persistReducer } from "redux-persist";
4 | import storage from "redux-persist/lib/storage";
5 |
6 | const persistConfig = {
7 | key: "root", // Key for the persisted state in storage
8 | storage, // Storage engine (e.g., localStorage)
9 | safelist: ["auth"], // Array of reducers to persist (only 'auth' in this case)
10 | };
11 |
12 | const persistedReducer = persistReducer(persistConfig, rootReducer)
13 |
14 | const store = configureStore({
15 | reducer: persistedReducer,
16 | middleware: (getDefaultMiddleware) => getDefaultMiddleware({
17 | serializableCheck: false,
18 | }),
19 | });
20 |
21 | const persistor = persistStore(store);
22 |
23 | export {persistor, store};
24 |
--------------------------------------------------------------------------------
/src/router.tsx:
--------------------------------------------------------------------------------
1 | import { Navigate, createBrowserRouter, useLocation } from 'react-router-dom'
2 | import GeneralError from './pages/errors/general-error'
3 | import NotFoundError from './pages/errors/not-found-error'
4 | import MaintenanceError from './pages/errors/maintenance-error'
5 | import { toast } from './components/ui/use-toast';
6 | import { ToastAction } from "@/components/ui/toast"
7 |
8 | function RequireAuth({ children }: { children: JSX.Element }) {
9 | let auth = useAuth();
10 | let location = useLocation();
11 |
12 | if (!auth) {
13 | toast({
14 | variant: "destructive",
15 | title: "Invalid Token",
16 | action: Try again,
17 | })
18 | return ;
19 | }
20 |
21 | return children;
22 | }
23 |
24 | function useAuth() {
25 | return sessionStorage.getItem('token');
26 | }
27 |
28 | const router = createBrowserRouter([
29 | // Auth routes
30 | {
31 | path: '/sign-in',
32 | lazy: async () => ({
33 | Component: (await import('./pages/auth/sign-in')).default,
34 | }),
35 | },
36 | { path: '/', element: sessionStorage.getItem('token') ? :},
37 | {
38 | path: '/sign-in-2',
39 | lazy: async () => ({
40 | Component: (await import('./pages/auth/sign-in-2')).default,
41 | }),
42 | },
43 | {
44 | path: '/sign-up',
45 | lazy: async () => ({
46 | Component: (await import('./pages/auth/sign-up')).default,
47 | }),
48 | },
49 | {
50 | path: '/forgot-password',
51 | lazy: async () => ({
52 | Component: (await import('./pages/auth/forgot-password')).default,
53 | }),
54 | },
55 | {
56 | path: '/otp',
57 | lazy: async () => ({
58 | Component: (await import('./pages/auth/otp')).default,
59 | }),
60 | },
61 |
62 | // Main routes
63 | {
64 | path: '/',
65 | lazy: async () => {
66 | const AppShell = await import('./components/app-shell')
67 | const DashboardComponent = AppShell.default;
68 | return {
69 | Component: () => (
70 |
71 |
72 |
73 | ),
74 | }
75 | },
76 | children: [
77 | {
78 | path: '/dashboard',
79 | lazy: async () => ({
80 | Component: (await import('@/pages/dashboard')).default,
81 | }),
82 | },
83 | {
84 | path: 'tasks',
85 | lazy: async () => ({
86 | Component: (await import('@/pages/tasks')).default,
87 | }),
88 | },
89 | {
90 | path: 'chats',
91 | lazy: async () => ({
92 | Component: (await import('@/components/coming-soon')).default,
93 | }),
94 | },
95 | {
96 | path: 'apps',
97 | lazy: async () => ({
98 | Component: (await import('@/pages/apps')).default,
99 | }),
100 | },
101 | {
102 | path: 'users',
103 | lazy: async () => ({
104 | Component: (await import('@/components/coming-soon')).default,
105 | }),
106 | },
107 | {
108 | path: 'analysis',
109 | lazy: async () => ({
110 | Component: (await import('@/components/coming-soon')).default,
111 | }),
112 | },
113 | {
114 | path: 'extra-components',
115 | lazy: async () => ({
116 | Component: (await import('@/pages/extra-components')).default,
117 | }),
118 | },
119 | {
120 | path: 'settings',
121 | lazy: async () => ({
122 | Component: (await import('./pages/settings')).default,
123 | }),
124 | errorElement: ,
125 | children: [
126 | {
127 | index: true,
128 | lazy: async () => ({
129 | Component: (await import('./pages/settings/profile')).default,
130 | }),
131 | },
132 | {
133 | path: 'account',
134 | lazy: async () => ({
135 | Component: (await import('./pages/settings/account')).default,
136 | }),
137 | },
138 | {
139 | path: 'appearance',
140 | lazy: async () => ({
141 | Component: (await import('./pages/settings/appearance')).default,
142 | }),
143 | },
144 | {
145 | path: 'notifications',
146 | lazy: async () => ({
147 | Component: (await import('./pages/settings/notifications'))
148 | .default,
149 | }),
150 | },
151 | {
152 | path: 'display',
153 | lazy: async () => ({
154 | Component: (await import('./pages/settings/display')).default,
155 | }),
156 | },
157 | {
158 | path: 'error-example',
159 | lazy: async () => ({
160 | Component: (await import('./pages/settings/error-example'))
161 | .default,
162 | }),
163 | errorElement: ,
164 | },
165 | ],
166 | },
167 | ],
168 | },
169 |
170 | // Error routes
171 | { path: '/500', Component: GeneralError },
172 | { path: '/404', Component: NotFoundError },
173 | { path: '/503', Component: MaintenanceError },
174 |
175 | // Fallback 404 route
176 | { path: '*', Component: NotFoundError },
177 | ])
178 |
179 | export default router
180 |
--------------------------------------------------------------------------------
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | darkMode: ["class"],
4 | content: [
5 | './pages/**/*.{ts,tsx}',
6 | './components/**/*.{ts,tsx}',
7 | './app/**/*.{ts,tsx}',
8 | './src/**/*.{ts,tsx}',
9 | ],
10 | prefix: "",
11 | theme: {
12 | container: {
13 | center: true,
14 | padding: "2rem",
15 | screens: {
16 | "2xl": "1400px",
17 | },
18 | },
19 | extend: {
20 | colors: {
21 | border: "hsl(var(--border))",
22 | input: "hsl(var(--input))",
23 | ring: "hsl(var(--ring))",
24 | background: "hsl(var(--background))",
25 | foreground: "hsl(var(--foreground))",
26 | primary: {
27 | DEFAULT: "hsl(var(--primary))",
28 | foreground: "hsl(var(--primary-foreground))",
29 | },
30 | secondary: {
31 | DEFAULT: "hsl(var(--secondary))",
32 | foreground: "hsl(var(--secondary-foreground))",
33 | },
34 | destructive: {
35 | DEFAULT: "hsl(var(--destructive))",
36 | foreground: "hsl(var(--destructive-foreground))",
37 | },
38 | muted: {
39 | DEFAULT: "hsl(var(--muted))",
40 | foreground: "hsl(var(--muted-foreground))",
41 | },
42 | accent: {
43 | DEFAULT: "hsl(var(--accent))",
44 | foreground: "hsl(var(--accent-foreground))",
45 | },
46 | popover: {
47 | DEFAULT: "hsl(var(--popover))",
48 | foreground: "hsl(var(--popover-foreground))",
49 | },
50 | card: {
51 | DEFAULT: "hsl(var(--card))",
52 | foreground: "hsl(var(--card-foreground))",
53 | },
54 | },
55 | borderRadius: {
56 | lg: "var(--radius)",
57 | md: "calc(var(--radius) - 2px)",
58 | sm: "calc(var(--radius) - 4px)",
59 | },
60 | keyframes: {
61 | "accordion-down": {
62 | from: { height: "0" },
63 | to: { height: "var(--radix-accordion-content-height)" },
64 | },
65 | "accordion-up": {
66 | from: { height: "var(--radix-accordion-content-height)" },
67 | to: { height: "0" },
68 | },
69 | },
70 | animation: {
71 | "accordion-down": "accordion-down 0.2s ease-out",
72 | "accordion-up": "accordion-up 0.2s ease-out",
73 | },
74 | },
75 | },
76 | plugins: [require("tailwindcss-animate")],
77 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "noEmit": true,
15 | "jsx": "react-jsx",
16 |
17 | /* Alias */
18 | "baseUrl": ".",
19 | "paths": {
20 | "@/*": ["./src/*"],
21 | },
22 |
23 | /* Linting */
24 | "strict": true,
25 | "noUnusedLocals": true,
26 | "noUnusedParameters": true,
27 | "noFallthroughCasesInSwitch": true,
28 | },
29 | "include": ["src"],
30 | "references": [{ "path": "./tsconfig.node.json" }],
31 | }
32 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true
8 | },
9 | "include": ["vite.config.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import path from 'path'
2 | import { defineConfig } from 'vite'
3 | import react from '@vitejs/plugin-react-swc'
4 |
5 | // https://vitejs.dev/config/
6 | export default defineConfig({
7 | plugins: [react()],
8 | resolve: {
9 | alias: {
10 | '@': path.resolve(__dirname, './src'),
11 | },
12 | },
13 | })
14 |
--------------------------------------------------------------------------------
|