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/toggle-group.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group';
3 | import { VariantProps } from 'class-variance-authority';
4 |
5 | import { cn } from '@/lib/utils';
6 | import { toggleVariants } from '@/components/ui/toggle';
7 |
8 | const ToggleGroupContext = React.createContext<
9 | VariantProps
10 | >({
11 | size: 'default',
12 | variant: 'default'
13 | });
14 |
15 | const ToggleGroup = React.forwardRef<
16 | React.ElementRef,
17 | React.ComponentPropsWithoutRef &
18 | VariantProps
19 | >(({ className, variant, size, children, ...props }, ref) => (
20 |
25 |
26 | {children}
27 |
28 |
29 | ));
30 |
31 | ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName;
32 |
33 | const ToggleGroupItem = React.forwardRef<
34 | React.ElementRef,
35 | React.ComponentPropsWithoutRef &
36 | VariantProps
37 | >(({ className, children, variant, size, ...props }, ref) => {
38 | const context = React.useContext(ToggleGroupContext);
39 |
40 | return (
41 |
52 | {children}
53 |
54 | );
55 | });
56 |
57 | ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName;
58 |
59 | export { ToggleGroup, ToggleGroupItem };
60 |
--------------------------------------------------------------------------------
/src/components/ui/toggle.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as TogglePrimitive from '@radix-ui/react-toggle';
3 | import { cva, type VariantProps } from 'class-variance-authority';
4 |
5 | import { cn } from '@/lib/utils';
6 |
7 | const toggleVariants = cva(
8 | 'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground',
9 | {
10 | variants: {
11 | variant: {
12 | default: 'bg-transparent',
13 | outline:
14 | 'border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground'
15 | },
16 | size: {
17 | default: 'h-9 px-3',
18 | sm: 'h-8 px-2',
19 | lg: 'h-10 px-3'
20 | }
21 | },
22 | defaultVariants: {
23 | variant: 'default',
24 | size: 'default'
25 | }
26 | }
27 | );
28 |
29 | const Toggle = React.forwardRef<
30 | React.ElementRef,
31 | React.ComponentPropsWithoutRef &
32 | VariantProps
33 | >(({ className, variant, size, ...props }, ref) => (
34 |
39 | ));
40 |
41 | Toggle.displayName = TogglePrimitive.Root.displayName;
42 |
43 | export { Toggle, toggleVariants };
44 |
--------------------------------------------------------------------------------
/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 { ToastActionElement, ToastProps } from '@/components/ui/toast';
5 |
6 | const TOAST_LIMIT = 1;
7 | const TOAST_REMOVE_DELAY = 1000000;
8 |
9 | type ToasterToast = ToastProps & {
10 | id: string;
11 | title?: React.ReactNode;
12 | description?: React.ReactNode;
13 | action?: ToastActionElement;
14 | };
15 |
16 | const actionTypes = {
17 | ADD_TOAST: 'ADD_TOAST',
18 | UPDATE_TOAST: 'UPDATE_TOAST',
19 | DISMISS_TOAST: 'DISMISS_TOAST',
20 | REMOVE_TOAST: 'REMOVE_TOAST'
21 | } as const;
22 |
23 | let count = 0;
24 |
25 | function genId() {
26 | count = (count + 1) % Number.MAX_SAFE_INTEGER;
27 | return count.toString();
28 | }
29 |
30 | type ActionType = typeof actionTypes;
31 |
32 | type Action =
33 | | {
34 | type: ActionType['ADD_TOAST'];
35 | toast: ToasterToast;
36 | }
37 | | {
38 | type: ActionType['UPDATE_TOAST'];
39 | toast: Partial;
40 | }
41 | | {
42 | type: ActionType['DISMISS_TOAST'];
43 | toastId?: ToasterToast['id'];
44 | }
45 | | {
46 | type: ActionType['REMOVE_TOAST'];
47 | toastId?: ToasterToast['id'];
48 | };
49 |
50 | interface State {
51 | toasts: ToasterToast[];
52 | }
53 |
54 | const toastTimeouts = new Map>();
55 |
56 | const addToRemoveQueue = (toastId: string) => {
57 | if (toastTimeouts.has(toastId)) {
58 | return;
59 | }
60 |
61 | const timeout = setTimeout(() => {
62 | toastTimeouts.delete(toastId);
63 | dispatch({
64 | type: 'REMOVE_TOAST',
65 | toastId: toastId
66 | });
67 | }, TOAST_REMOVE_DELAY);
68 |
69 | toastTimeouts.set(toastId, timeout);
70 | };
71 |
72 | export const reducer = (state: State, action: Action): State => {
73 | switch (action.type) {
74 | case 'ADD_TOAST':
75 | return {
76 | ...state,
77 | toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT)
78 | };
79 |
80 | case 'UPDATE_TOAST':
81 | return {
82 | ...state,
83 | toasts: state.toasts.map((t) =>
84 | t.id === action.toast.id ? { ...t, ...action.toast } : t
85 | )
86 | };
87 |
88 | case 'DISMISS_TOAST': {
89 | const { toastId } = action;
90 |
91 | // ! Side effects ! - This could be extracted into a dismissToast() action,
92 | // but I'll keep it here for simplicity
93 | if (toastId) {
94 | addToRemoveQueue(toastId);
95 | } else {
96 | state.toasts.forEach((toast) => {
97 | addToRemoveQueue(toast.id);
98 | });
99 | }
100 |
101 | return {
102 | ...state,
103 | toasts: state.toasts.map((t) =>
104 | t.id === toastId || toastId === undefined
105 | ? {
106 | ...t,
107 | open: false
108 | }
109 | : t
110 | )
111 | };
112 | }
113 | case 'REMOVE_TOAST':
114 | if (action.toastId === undefined) {
115 | return {
116 | ...state,
117 | toasts: []
118 | };
119 | }
120 | return {
121 | ...state,
122 | toasts: state.toasts.filter((t) => t.id !== action.toastId)
123 | };
124 | }
125 | };
126 |
127 | const listeners: Array<(state: State) => void> = [];
128 |
129 | let memoryState: State = { toasts: [] };
130 |
131 | function dispatch(action: Action) {
132 | memoryState = reducer(memoryState, action);
133 | listeners.forEach((listener) => {
134 | listener(memoryState);
135 | });
136 | }
137 |
138 | type Toast = Omit;
139 |
140 | function toast({ ...props }: Toast) {
141 | const id = genId();
142 |
143 | const update = (props: ToasterToast) =>
144 | dispatch({
145 | type: 'UPDATE_TOAST',
146 | toast: { ...props, id }
147 | });
148 | const dismiss = () => dispatch({ type: 'DISMISS_TOAST', toastId: id });
149 |
150 | dispatch({
151 | type: 'ADD_TOAST',
152 | toast: {
153 | ...props,
154 | id,
155 | open: true,
156 | onOpenChange: (open) => {
157 | if (!open) dismiss();
158 | }
159 | }
160 | });
161 |
162 | return {
163 | id: id,
164 | dismiss,
165 | update
166 | };
167 | }
168 |
169 | function useToast() {
170 | const [state, setState] = React.useState(memoryState);
171 |
172 | React.useEffect(() => {
173 | listeners.push(setState);
174 | return () => {
175 | const index = listeners.indexOf(setState);
176 | if (index > -1) {
177 | listeners.splice(index, 1);
178 | }
179 | };
180 | }, [state]);
181 |
182 | return {
183 | ...state,
184 | toast,
185 | dismiss: (toastId?: string) => dispatch({ type: 'DISMISS_TOAST', toastId })
186 | };
187 | }
188 |
189 | export { useToast, toast };
190 |
--------------------------------------------------------------------------------
/src/constants/data.ts:
--------------------------------------------------------------------------------
1 | import { NavItem } from '@/types';
2 |
3 | export const navItems: NavItem[] = [
4 | {
5 | title: 'Dashboard',
6 | href: '/',
7 | icon: 'dashboard',
8 | label: 'Dashboard'
9 | },
10 | {
11 | title: 'Students',
12 | href: '/student',
13 | icon: 'user',
14 | label: 'Student'
15 | },
16 | {
17 | title: 'Login',
18 | href: '/login',
19 | icon: 'login',
20 | label: 'Login'
21 | }
22 | ];
23 |
24 | export const users = [
25 | {
26 | id: 1,
27 | name: 'Candice Schiner',
28 | company: 'Dell',
29 | role: 'Frontend Developer',
30 | verified: false,
31 | status: 'Active'
32 | },
33 | {
34 | id: 2,
35 | name: 'John Doe',
36 | company: 'TechCorp',
37 | role: 'Backend Developer',
38 | verified: true,
39 | status: 'Active'
40 | },
41 | {
42 | id: 3,
43 | name: 'Alice Johnson',
44 | company: 'WebTech',
45 | role: 'UI Designer',
46 | verified: true,
47 | status: 'Active'
48 | },
49 | {
50 | id: 4,
51 | name: 'David Smith',
52 | company: 'Innovate Inc.',
53 | role: 'Fullstack Developer',
54 | verified: false,
55 | status: 'Inactive'
56 | },
57 | {
58 | id: 5,
59 | name: 'Emma Wilson',
60 | company: 'TechGuru',
61 | role: 'Product Manager',
62 | verified: true,
63 | status: 'Active'
64 | },
65 | {
66 | id: 6,
67 | name: 'James Brown',
68 | company: 'CodeGenius',
69 | role: 'QA Engineer',
70 | verified: false,
71 | status: 'Active'
72 | },
73 | {
74 | id: 7,
75 | name: 'Laura White',
76 | company: 'SoftWorks',
77 | role: 'UX Designer',
78 | verified: true,
79 | status: 'Active'
80 | },
81 | {
82 | id: 8,
83 | name: 'Michael Lee',
84 | company: 'DevCraft',
85 | role: 'DevOps Engineer',
86 | verified: false,
87 | status: 'Active'
88 | },
89 | {
90 | id: 9,
91 | name: 'Olivia Green',
92 | company: 'WebSolutions',
93 | role: 'Frontend Developer',
94 | verified: true,
95 | status: 'Active'
96 | },
97 | {
98 | id: 10,
99 | name: 'Robert Taylor',
100 | company: 'DataTech',
101 | role: 'Data Analyst',
102 | verified: false,
103 | status: 'Active'
104 | }
105 | ];
106 |
107 | export const dashboardCard = [
108 | {
109 | date: 'Today',
110 | total: 2000,
111 | role: 'Students',
112 | color: 'bg-[#EC4D61] bg-opacity-40'
113 | },
114 | {
115 | date: 'Today',
116 | total: 2000,
117 | role: 'Teachers',
118 | color: 'bg-[#FFEB95] bg-opacity-100'
119 | },
120 | {
121 | date: 'Today',
122 | total: 2000,
123 | role: 'Parents',
124 | color: 'bg-[#84BD47] bg-opacity-30'
125 | },
126 | {
127 | date: 'Today',
128 | total: 2000,
129 | role: 'Schools',
130 | color: 'bg-[#D289FF] bg-opacity-30'
131 | }
132 | ];
133 |
134 | export type Employee = {
135 | id: number;
136 | first_name: string;
137 | last_name: string;
138 | email: string;
139 | phone: string;
140 | gender: string;
141 | date_of_birth: string; // Consider using a proper date type if possible
142 | street: string;
143 | city: string;
144 | state: string;
145 | country: string;
146 | zipcode: string;
147 | longitude?: number; // Optional field
148 | latitude?: number; // Optional field
149 | job: string;
150 | profile_picture?: string | null; // Profile picture can be a string (URL) or null (if no picture)
151 | };
152 |
--------------------------------------------------------------------------------
/src/hooks/use-sidebar.tsx:
--------------------------------------------------------------------------------
1 | import React, { createContext, useContext, useState } from 'react';
2 |
3 | const SidebarContext = createContext<{
4 | isMinimized: boolean;
5 | toggle: () => void;
6 | }>({
7 | isMinimized: false,
8 | toggle: () => {}
9 | });
10 |
11 | export const useSidebar = () => useContext(SidebarContext);
12 |
13 | export const SidebarProvider: React.FC<{ children: React.ReactNode }> = ({
14 | children
15 | }) => {
16 | const [isMinimized, setIsMinimized] = useState(false);
17 |
18 | const toggle = () => {
19 | setIsMinimized(!isMinimized);
20 | };
21 |
22 | return (
23 |
24 | {children}
25 |
26 | );
27 | };
28 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&display=swap');
2 | @tailwind base;
3 | @tailwind components;
4 | @tailwind utilities;
5 |
6 | @layer base {
7 | :root {
8 | --background: 0 0% 100%;
9 | --foreground: 222.2 84% 4.9%;
10 | --card: 0 0% 100%;
11 | --card-foreground: 222.2 84% 4.9%;
12 | --popover: 0 0% 100%;
13 | --popover-foreground: 222.2 84% 4.9%;
14 | --primary: 222.2 47.4% 11.2%;
15 | --primary-foreground: 210 40% 98%;
16 | --secondary: 210 40% 96.1%;
17 | --secondary-foreground: 222.2 47.4% 11.2%;
18 | --muted: 210 40% 96.1%;
19 | --muted-foreground: 215.4 16.3% 46.9%;
20 | --accent: 210 40% 96.1%;
21 | --accent-foreground: 222.2 47.4% 11.2%;
22 | --destructive: 0 84.2% 60.2%;
23 | --destructive-foreground: 210 40% 98%;
24 | --border: 214.3 31.8% 91.4%;
25 | --input: 214.3 31.8% 91.4%;
26 | --ring: 222.2 84% 4.9%;
27 | --radius: 0.5rem;
28 | --chart-1: 12 76% 61%;
29 | --chart-2: 173 58% 39%;
30 | --chart-3: 197 37% 24%;
31 | --chart-4: 43 74% 66%;
32 | --chart-5: 27 87% 67%;
33 | }
34 |
35 | .dark {
36 | --background: 222.2 84% 4.9%;
37 | --foreground: 210 40% 98%;
38 | --card: 222.2 84% 4.9%;
39 | --card-foreground: 210 40% 98%;
40 | --popover: 222.2 84% 4.9%;
41 | --popover-foreground: 210 40% 98%;
42 | --primary: 210 40% 98%;
43 | --primary-foreground: 222.2 47.4% 11.2%;
44 | --secondary: 217.2 32.6% 17.5%;
45 | --secondary-foreground: 210 40% 98%;
46 | --muted: 217.2 32.6% 17.5%;
47 | --muted-foreground: 215 20.2% 65.1%;
48 | --accent: 217.2 32.6% 17.5%;
49 | --accent-foreground: 210 40% 98%;
50 | --destructive: 0 62.8% 30.6%;
51 | --destructive-foreground: 210 40% 98%;
52 | --border: 217.2 32.6% 17.5%;
53 | --input: 217.2 32.6% 17.5%;
54 | --ring: 212.7 26.8% 83.9;
55 | --chart-1: 220 70% 50%;
56 | --chart-2: 160 60% 45%;
57 | --chart-3: 30 80% 55%;
58 | --chart-4: 280 65% 60%;
59 | --chart-5: 340 75% 55%;
60 | }
61 | }
62 |
63 | @layer base {
64 | * {
65 | @apply border-border;
66 | }
67 | body {
68 | @apply bg-background text-foreground;
69 | }
70 | }
71 |
72 | @layer base {
73 | html {
74 | font-family: 'Montserrat', sans-serif;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/lib/api.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | // ---------------------------- Student API ------------------------------------------------- //
3 | // export async function resendEmail(email: string) {
4 | // try {
5 | // const res = await axios.post("/auth/register/resend-email/", { email });
6 | // return res.data;
7 | // } catch (error) {
8 | // console.log(error);
9 | // return error;
10 | // }
11 | // }
12 |
13 | export async function getStudents(
14 | offset: number,
15 | pageLimit: number,
16 | country: string
17 | ) {
18 | try {
19 | const res = await axios.get(
20 | `https://api.slingacademy.com/v1/sample-data/users?offset=${offset}&limit=${pageLimit}` +
21 | (country ? `&search=${country}` : '')
22 | );
23 | return res.data;
24 | } catch (error) {
25 | console.log(error);
26 | return error;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/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 App from './App.tsx';
4 | import './index.css';
5 |
6 | ReactDOM.createRoot(document.getElementById('root')!).render(
7 |
8 |
9 |
10 | );
11 |
--------------------------------------------------------------------------------
/src/pages/auth/signin/components/user-auth-form.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from '@/components/ui/button';
2 | import {
3 | Form,
4 | FormControl,
5 | FormField,
6 | FormItem,
7 | FormLabel,
8 | FormMessage
9 | } from '@/components/ui/form';
10 | import { Input } from '@/components/ui/input';
11 | import { useRouter } from '@/routes/hooks';
12 | import { zodResolver } from '@hookform/resolvers/zod';
13 | import { useState } from 'react';
14 | import { useForm } from 'react-hook-form';
15 | import * as z from 'zod';
16 |
17 | const formSchema = z.object({
18 | email: z.string().email({ message: 'Enter a valid email address' })
19 | });
20 |
21 | type UserFormValue = z.infer;
22 |
23 | export default function UserAuthForm() {
24 | const router = useRouter();
25 | const [loading] = useState(false);
26 | const defaultValues = {
27 | email: 'demo@gmail.com'
28 | };
29 | const form = useForm({
30 | resolver: zodResolver(formSchema),
31 | defaultValues
32 | });
33 |
34 | const onSubmit = async (data: UserFormValue) => {
35 | console.log('data', data);
36 | router.push('/');
37 | };
38 |
39 | return (
40 | <>
41 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 | Or continue with
77 |
78 |
79 |
80 | >
81 | );
82 | }
83 |
--------------------------------------------------------------------------------
/src/pages/auth/signin/index.tsx:
--------------------------------------------------------------------------------
1 | import UserAuthForm from './components/user-auth-form';
2 | import { buttonVariants } from '@/components/ui/button';
3 | import { cn } from '@/lib/utils';
4 | import { Link } from 'react-router-dom';
5 |
6 | export default function SignInPage() {
7 | return (
8 |
9 |
16 | Login
17 |
18 |
19 |
20 |
21 |
31 |
32 |
33 | Logo
34 |
35 |
36 |
37 |
38 | “This library has saved me countless hours of work and
39 | helped me deliver stunning designs to my clients faster than ever
40 | before.”
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | Create an account
51 |
52 |
53 | Enter your email below to create your account
54 |
55 |
56 |
57 |
58 | By clicking continue, you agree to our{' '}
59 |
63 | Terms of Service
64 | {' '}
65 | and{' '}
66 |
70 | Privacy Policy
71 |
72 | .
73 |
74 |
75 |
76 |
77 | );
78 | }
79 |
--------------------------------------------------------------------------------
/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 default function Overview() {
55 | return (
56 |
57 |
58 |
65 | `$${value}`}
71 | />
72 |
73 |
74 |
75 | );
76 | }
77 |
--------------------------------------------------------------------------------
/src/pages/dashboard/components/recent-sales.tsx:
--------------------------------------------------------------------------------
1 | import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
2 |
3 | export default 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/form/index.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from '@/components/ui/button';
2 | import {
3 | Form,
4 | FormField,
5 | FormItem,
6 | FormLabel,
7 | FormControl,
8 | FormDescription,
9 | FormMessage
10 | } from '@/components/ui/form';
11 | import { Input } from '@/components/ui/input';
12 | import { zodResolver } from '@hookform/resolvers/zod';
13 | import { useForm } from 'react-hook-form';
14 | import { z } from 'zod';
15 |
16 | const formSchema = z.object({
17 | name: z.string().min(2, {
18 | message: 'Username must be at least 2 characters.'
19 | }),
20 | email: z.string().email({
21 | message: 'Invalid email address.'
22 | }),
23 | dob: z
24 | .date()
25 | .min(new Date(), {
26 | message: 'Date of birth must be in the future.'
27 | })
28 | .optional()
29 | });
30 |
31 | export default function FormPage() {
32 | const form = useForm>({
33 | resolver: zodResolver(formSchema),
34 | defaultValues: {
35 | name: ''
36 | }
37 | });
38 |
39 | const {
40 | formState: { errors, isSubmitting }
41 | } = form;
42 |
43 | console.log('formState', errors);
44 |
45 | const onSubmit = async (data: z.infer) => {
46 | console.log('onSubmit', data);
47 | };
48 |
49 | return (
50 |
51 |
89 |
90 |
91 | );
92 | }
93 |
--------------------------------------------------------------------------------
/src/pages/not-found/index.tsx:
--------------------------------------------------------------------------------
1 | import { useRouter } from '@/routes/hooks';
2 | import { Button } from '@/components/ui/button';
3 |
4 | export default function NotFound() {
5 | const router = useRouter();
6 |
7 | return (
8 |
9 |
10 | 404
11 |
12 |
13 | Something's missing
14 |
15 |
16 | Sorry, the page you are looking for doesn't exist or has been
17 | moved.
18 |
19 |
20 | router.back()} variant="default" size="lg">
21 | Go back
22 |
23 | router.push('/')} variant="ghost" size="lg">
24 | Back to Home
25 |
26 |
27 |
28 | );
29 | }
30 |
--------------------------------------------------------------------------------
/src/pages/students/StudentDetailPage.tsx:
--------------------------------------------------------------------------------
1 | import Heading from '@/components/shared/heading';
2 | import { Badge } from '@/components/ui/badge';
3 | import { Button } from '@/components/ui/button';
4 | import { Card, CardContent, CardHeader } from '@/components/ui/card';
5 | import { useRouter } from '@/routes/hooks';
6 | import { ChevronLeftIcon, ShareIcon } from 'lucide-react';
7 | import { useSearchParams } from 'react-router-dom';
8 | import InterestChannel from './components/interest-channel';
9 | import StudentFeedTable from './components/student-feed-table';
10 | import { useGetStudents } from './queries/queries';
11 |
12 | export default function StudentDetailPage() {
13 | const [searchParams] = useSearchParams();
14 | const page = Number(searchParams.get('page') || 1);
15 | const pageLimit = Number(searchParams.get('limit') || 10);
16 | const country = searchParams.get('search') || null;
17 | const offset = (page - 1) * pageLimit;
18 | const { data, isLoading } = useGetStudents(offset, pageLimit, country);
19 | const users = data?.users;
20 | const totalUsers = data?.total_users; //1000
21 | const pageCount = Math.ceil(totalUsers / pageLimit);
22 | const router = useRouter();
23 | if (isLoading) {
24 | return Loading!!! ;
25 | }
26 | return (
27 |
28 |
29 |
30 |
31 |
32 |
33 | Share
34 |
35 | router.back()}>
36 |
37 | Back
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | Profile
46 | Active
47 |
48 |
49 |
53 |
54 |
55 |
56 |
57 | About Me
58 |
59 |
60 | Hello! I'm Srikkath, your dedicated admin at Kutubi, ensuring a
61 | seamless and enriching experience for teachers, students, and
62 | parents. Feel free to reach out for any assistance or feedback
63 |
64 |
65 |
66 |
67 | Last Login
68 |
69 |
70 | 12 Aug 2022 9:30 AM
71 |
72 |
73 |
74 | {/* contact information */}
75 |
76 |
77 | Contact Information
78 |
79 |
80 |
81 |
82 |
83 |
First Name
84 |
John
85 |
86 |
87 |
Last Name
88 |
Doe
89 |
90 |
91 |
User Name
92 |
John
93 |
94 |
98 |
99 |
Position
100 |
Super Admin
101 |
102 |
103 |
Department
104 |
Kutubi
105 |
106 |
107 |
Contact Email
108 |
ElonMusk@x.com
109 |
110 |
111 |
Contact Number
112 |
Nil
113 |
114 |
115 |
City
116 |
Dubai
117 |
118 |
119 |
Language
120 |
English
121 |
122 |
123 |
Date of Birth
124 |
26/4/1989
125 |
126 |
127 |
Social Media
128 |
x
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
145 |
146 | );
147 | }
148 |
--------------------------------------------------------------------------------
/src/pages/students/components/bio.tsx:
--------------------------------------------------------------------------------
1 | export default function Bio() {
2 | return (
3 |
4 |
Bio
5 |
6 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod
7 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
8 | veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
9 | commodo consequat.
10 |
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/src/pages/students/components/count-card.tsx:
--------------------------------------------------------------------------------
1 | export default function CountCard() {
2 | return (
3 |
4 |
Count
5 |
6 |
7 |
Books
8 |
9 | Completed{' '}
10 | 50
11 |
12 |
13 | On-going{' '}
14 | 5
15 |
16 |
17 |
18 |
19 |
Videos
20 |
Watched
21 |
22 |
23 | 10
24 |
25 |
26 |
27 |
28 |
29 |
Task
30 |
31 | Completed{' '}
32 | 5
33 |
34 |
35 | Not completed{' '}
36 | 2
37 |
38 |
39 |
40 |
41 |
Word
42 |
Count
43 |
44 |
45 | 710
46 |
47 |
48 |
49 |
50 | );
51 | }
52 |
--------------------------------------------------------------------------------
/src/pages/students/components/feed.tsx:
--------------------------------------------------------------------------------
1 | export default function Feed() {
2 | return
;
3 | }
4 |
--------------------------------------------------------------------------------
/src/pages/students/components/interest-channel.tsx:
--------------------------------------------------------------------------------
1 | import { useSearchParams } from 'react-router-dom';
2 | import { useGetStudents } from '../queries/queries';
3 | import { useState } from 'react';
4 | import PaginationSection from '@/components/shared/pagination-section';
5 | import { GitHubLogoIcon } from '@radix-ui/react-icons';
6 |
7 | const InterestChannel = ({ title }: { title: string }) => {
8 | const [searchParams] = useSearchParams();
9 | const page = Number(searchParams.get('page') || 1);
10 | const [currentPage, setCurrentPage] = useState(page);
11 | const pageLimit = 6;
12 | const country = searchParams.get('search') || null;
13 | const offset = (currentPage - 1) * pageLimit;
14 | const { data } = useGetStudents(offset, pageLimit, country);
15 | const users = data?.users;
16 | console.log('users', users);
17 | const totalUsers = data?.total_users; //1000
18 |
19 | return (
20 |
21 |
22 |
23 | {title}
24 |
25 |
26 | {/* Mapping through repeatItems array to render the content 7 times */}
27 | {users?.map((item: any, index: number) => (
28 |
29 |
30 |
{item.city}
31 |
32 | ))}
33 |
34 | {/* pagination */}
35 |
41 |
42 | );
43 | };
44 |
45 | export default InterestChannel;
46 |
--------------------------------------------------------------------------------
/src/pages/students/components/parent-detail-card.tsx:
--------------------------------------------------------------------------------
1 | export default function ParentsDetailsCard() {
2 | return (
3 |
4 |
5 |
6 |
Parent's Name: Fathima Farhan
7 |
Gender: Female
8 |
Email: example@gmail.com
9 |
Phone: +972 13-589-745
10 |
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/src/pages/students/components/student-feed-table/cell-action.tsx:
--------------------------------------------------------------------------------
1 | import { AlertModal } from '@/components/shared/alert-modal';
2 | import { Button } from '@/components/ui/button';
3 | import {
4 | DropdownMenu,
5 | DropdownMenuContent,
6 | DropdownMenuItem,
7 | DropdownMenuLabel,
8 | DropdownMenuTrigger
9 | } from '@/components/ui/dropdown-menu';
10 | import { Employee } from '@/constants/data';
11 | import { Edit, MoreHorizontal, Trash } from 'lucide-react';
12 | import { useRouter } from '@/routes/hooks';
13 | import { useState } from 'react';
14 |
15 | interface CellActionProps {
16 | data: Employee;
17 | }
18 |
19 | export const CellAction: React.FC = ({ data }) => {
20 | const [loading] = useState(false);
21 | const [open, setOpen] = useState(false);
22 | const router = useRouter();
23 |
24 | const onConfirm = async () => {};
25 |
26 | return (
27 | <>
28 | setOpen(false)}
31 | onConfirm={onConfirm}
32 | loading={loading}
33 | />
34 |
35 |
36 |
37 | Open menu
38 |
39 |
40 |
41 |
42 | Actions
43 |
44 | router.push(`/dashboard/user/${data.id}`)}
46 | >
47 | Update
48 |
49 | setOpen(true)}>
50 | Delete
51 |
52 |
53 |
54 | >
55 | );
56 | };
57 |
--------------------------------------------------------------------------------
/src/pages/students/components/student-feed-table/columns.tsx:
--------------------------------------------------------------------------------
1 | import { Checkbox } from '@/components/ui/checkbox';
2 | import { Employee } from '@/constants/data';
3 | import { ColumnDef } from '@tanstack/react-table';
4 | import { CellAction } from './cell-action';
5 |
6 | export const columns: ColumnDef[] = [
7 | {
8 | id: 'select',
9 | header: ({ table }) => (
10 | table.toggleAllPageRowsSelected(!!value)}
13 | aria-label="Select all"
14 | />
15 | ),
16 | cell: ({ row }) => (
17 | row.toggleSelected(!!value)}
20 | aria-label="Select row"
21 | />
22 | ),
23 | enableSorting: false,
24 | enableHiding: false
25 | },
26 | {
27 | accessorKey: 'first_name',
28 | header: 'NAME'
29 | },
30 | {
31 | accessorKey: 'country',
32 | header: 'COUNTRY'
33 | },
34 | {
35 | accessorKey: 'email',
36 | header: 'EMAIL'
37 | },
38 | {
39 | accessorKey: 'job',
40 | header: 'COMPANY'
41 | },
42 | {
43 | accessorKey: 'gender',
44 | header: 'GENDER'
45 | },
46 | {
47 | id: 'actions',
48 | cell: ({ row }) =>
49 | }
50 | ];
51 |
--------------------------------------------------------------------------------
/src/pages/students/components/student-feed-table/index.tsx:
--------------------------------------------------------------------------------
1 | import DataTable from '@/components/shared/data-table';
2 | import { columns } from './columns';
3 | import StudentTableActions from './student-table-action';
4 |
5 | type TStudentsTableProps = {
6 | users: any;
7 | page: number;
8 | totalUsers: number;
9 | pageCount: number;
10 | };
11 |
12 | export default function StudentFeedTable({
13 | users,
14 | pageCount
15 | }: TStudentsTableProps) {
16 | return (
17 | <>
18 |
19 | {users && (
20 |
21 | )}
22 | >
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/src/pages/students/components/student-feed-table/student-table-action.tsx:
--------------------------------------------------------------------------------
1 | import PopupModal from '@/components/shared/popup-modal';
2 | import TableSearchInput from '@/components/shared/table-search-input';
3 | import { Button } from '@/components/ui/button';
4 | import StudentCreateForm from '../student-forms/student-create-form';
5 | import { DownloadIcon } from 'lucide-react';
6 |
7 | export default function StudentTableActions() {
8 | return (
9 |
10 |
13 |
14 |
15 |
16 | Download CSV
17 |
18 |
19 |
}
21 | />
22 |
23 |
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/src/pages/students/components/students-table/cell-action.tsx:
--------------------------------------------------------------------------------
1 | import { AlertModal } from '@/components/shared/alert-modal';
2 | import { Button } from '@/components/ui/button';
3 | import {
4 | DropdownMenu,
5 | DropdownMenuContent,
6 | DropdownMenuItem,
7 | DropdownMenuLabel,
8 | DropdownMenuTrigger
9 | } from '@/components/ui/dropdown-menu';
10 | import { Employee } from '@/constants/data';
11 | import { Edit, MoreHorizontal, Trash } from 'lucide-react';
12 | import { useRouter } from '@/routes/hooks';
13 | import { useState } from 'react';
14 |
15 | interface CellActionProps {
16 | data: Employee;
17 | }
18 |
19 | export const CellAction: React.FC = ({ data }) => {
20 | const [loading] = useState(false);
21 | const [open, setOpen] = useState(false);
22 | const router = useRouter();
23 |
24 | const onConfirm = async () => {};
25 |
26 | return (
27 | <>
28 | setOpen(false)}
31 | onConfirm={onConfirm}
32 | loading={loading}
33 | />
34 |
35 |
36 |
37 | Open menu
38 |
39 |
40 |
41 |
42 | Actions
43 |
44 | router.push(`/dashboard/user/${data.id}`)}
46 | >
47 | Update
48 |
49 | setOpen(true)}>
50 | Delete
51 |
52 |
53 |
54 | >
55 | );
56 | };
57 |
--------------------------------------------------------------------------------
/src/pages/students/components/students-table/columns.tsx:
--------------------------------------------------------------------------------
1 | import { Checkbox } from '@/components/ui/checkbox';
2 | import { Employee } from '@/constants/data';
3 | import { ColumnDef } from '@tanstack/react-table';
4 | import { CellAction } from './cell-action';
5 |
6 | export const columns: ColumnDef[] = [
7 | {
8 | id: 'select',
9 | header: ({ table }) => (
10 | table.toggleAllPageRowsSelected(!!value)}
13 | aria-label="Select all"
14 | />
15 | ),
16 | cell: ({ row }) => (
17 | row.toggleSelected(!!value)}
20 | aria-label="Select row"
21 | />
22 | ),
23 | enableSorting: false,
24 | enableHiding: false
25 | },
26 | {
27 | accessorKey: 'first_name',
28 | header: 'NAME'
29 | },
30 | {
31 | accessorKey: 'country',
32 | header: 'COUNTRY'
33 | },
34 | {
35 | accessorKey: 'email',
36 | header: 'EMAIL'
37 | },
38 | {
39 | accessorKey: 'job',
40 | header: 'COMPANY'
41 | },
42 | {
43 | accessorKey: 'gender',
44 | header: 'GENDER'
45 | },
46 | {
47 | id: 'actions',
48 | cell: ({ row }) =>
49 | }
50 | ];
51 |
--------------------------------------------------------------------------------
/src/pages/students/components/students-table/index.tsx:
--------------------------------------------------------------------------------
1 | import DataTable from '@/components/shared/data-table';
2 | import { columns } from './columns';
3 | import StudentTableActions from './student-table-action';
4 |
5 | type TStudentsTableProps = {
6 | users: any;
7 | page: number;
8 | totalUsers: number;
9 | pageCount: number;
10 | };
11 |
12 | export default function StudentsTable({
13 | users,
14 | pageCount
15 | }: TStudentsTableProps) {
16 | return (
17 | <>
18 |
19 | {users && (
20 |
21 | )}
22 | >
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/src/pages/students/components/students-table/student-table-action.tsx:
--------------------------------------------------------------------------------
1 | import PopupModal from '@/components/shared/popup-modal';
2 | import TableSearchInput from '@/components/shared/table-search-input';
3 | import StudentCreateForm from '../student-forms/student-create-form';
4 |
5 | export default function StudentTableActions() {
6 | return (
7 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/src/pages/students/components/time-spent-card.tsx:
--------------------------------------------------------------------------------
1 | import { Bar, BarChart, ResponsiveContainer, XAxis, YAxis } from 'recharts';
2 |
3 | const data = [
4 | {
5 | name: 'Reading',
6 | total: Math.floor(Math.random() * 6)
7 | },
8 | {
9 | name: 'Watching Videos',
10 | total: Math.floor(Math.random() * 6)
11 | },
12 | {
13 | name: 'App',
14 | total: Math.floor(Math.random() * 6)
15 | }
16 | ];
17 |
18 | export default function Overview() {
19 | const colors = ['#adfa1d', '#faad1d', '#1dcaff'];
20 |
21 | return (
22 | <>
23 |
24 |
Time Spent (Today)
25 |
26 |
27 |
28 |
35 | `${value}`}
41 | domain={[0, 5]}
42 | ticks={[0, 1, 2, 3, 4, 5]}
43 | />
44 |
50 |
51 |
52 | >
53 | );
54 | }
55 |
--------------------------------------------------------------------------------
/src/pages/students/index.tsx:
--------------------------------------------------------------------------------
1 | import PageHead from '@/components/shared/page-head';
2 | import { useGetStudents } from './queries/queries';
3 | import StudentsTable from './components/students-table';
4 | import { useSearchParams } from 'react-router-dom';
5 | import { DataTableSkeleton } from '@/components/shared/data-table-skeleton';
6 | import { Breadcrumbs } from '@/components/shared/breadcrumbs';
7 |
8 | export default function StudentPage() {
9 | const [searchParams] = useSearchParams();
10 | const page = Number(searchParams.get('page') || 1);
11 | const pageLimit = Number(searchParams.get('limit') || 10);
12 | const country = searchParams.get('search') || null;
13 | const offset = (page - 1) * pageLimit;
14 | const { data, isLoading } = useGetStudents(offset, pageLimit, country);
15 | const users = data?.users;
16 | const totalUsers = data?.total_users; //1000
17 | const pageCount = Math.ceil(totalUsers / pageLimit);
18 |
19 | if (isLoading) {
20 | return (
21 |
22 |
27 |
28 | );
29 | }
30 |
31 | return (
32 |
47 | );
48 | }
49 |
--------------------------------------------------------------------------------
/src/pages/students/queries/queries.ts:
--------------------------------------------------------------------------------
1 | import { getStudents } from '@/lib/api';
2 | import { useQuery } from '@tanstack/react-query';
3 |
4 | export const useGetStudents = (offset, pageLimit, country) => {
5 | return useQuery({
6 | queryKey: ['students', offset, pageLimit, country],
7 | queryFn: async () => getStudents(offset, pageLimit, country)
8 | });
9 | };
10 |
--------------------------------------------------------------------------------
/src/providers/index.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from '@/components/ui/button';
2 | import { useRouter } from '@/routes/hooks';
3 | import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
4 | import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
5 | import { Suspense } from 'react';
6 | import { ErrorBoundary, FallbackProps } from 'react-error-boundary';
7 | import { HelmetProvider } from 'react-helmet-async';
8 | import { BrowserRouter } from 'react-router-dom';
9 | import ThemeProvider from './theme-provider';
10 | import { SidebarProvider } from '@/hooks/use-sidebar';
11 |
12 | export const queryClient = new QueryClient();
13 |
14 | const ErrorFallback = ({ error }: FallbackProps) => {
15 | const router = useRouter();
16 | console.log('error', error);
17 | return (
18 |
22 |
23 | Ooops, something went wrong :({' '}
24 |
25 |
{error.message}
26 |
{error.stack}
27 |
router.back()}>
28 | Go back
29 |
30 |
31 | );
32 | };
33 |
34 | export default function AppProvider({
35 | children
36 | }: {
37 | children: React.ReactNode;
38 | }) {
39 | return (
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | {children}
48 |
49 |
50 |
51 |
52 |
53 |
54 | );
55 | }
56 |
--------------------------------------------------------------------------------
/src/providers/theme-provider.tsx:
--------------------------------------------------------------------------------
1 | import { createContext, useContext, useEffect, useState } from 'react';
2 |
3 | type Theme = 'dark' | 'light' | 'system';
4 |
5 | type ThemeProviderProps = {
6 | children: React.ReactNode;
7 | defaultTheme?: Theme;
8 | storageKey?: string;
9 | };
10 |
11 | type ThemeProviderState = {
12 | theme: Theme;
13 | setTheme: (theme: Theme) => void;
14 | };
15 |
16 | const initialState: ThemeProviderState = {
17 | theme: 'system',
18 | setTheme: () => null
19 | };
20 |
21 | const ThemeProviderContext = createContext(initialState);
22 |
23 | export default function ThemeProvider({
24 | children,
25 | defaultTheme = 'system',
26 | storageKey = 'vite-ui-theme',
27 | ...props
28 | }: ThemeProviderProps) {
29 | const [theme, setTheme] = useState(
30 | () => (localStorage.getItem(storageKey) as Theme) || defaultTheme
31 | );
32 |
33 | useEffect(() => {
34 | const root = window.document.documentElement;
35 |
36 | root.classList.remove('light', 'dark');
37 |
38 | if (theme === 'system') {
39 | const systemTheme = window.matchMedia('(prefers-color-scheme: dark)')
40 | .matches
41 | ? 'dark'
42 | : 'light';
43 |
44 | root.classList.add(systemTheme);
45 | return;
46 | }
47 |
48 | root.classList.add(theme);
49 | }, [theme]);
50 |
51 | const value = {
52 | theme,
53 | setTheme: (theme: Theme) => {
54 | localStorage.setItem(storageKey, theme);
55 | setTheme(theme);
56 | }
57 | };
58 |
59 | return (
60 |
61 | {children}
62 |
63 | );
64 | }
65 |
66 | export const useTheme = () => {
67 | const context = useContext(ThemeProviderContext);
68 |
69 | if (context === undefined)
70 | throw new Error('useTheme must be used within a ThemeProvider');
71 |
72 | return context;
73 | };
74 |
--------------------------------------------------------------------------------
/src/routes/hooks/index.tsx:
--------------------------------------------------------------------------------
1 | export { usePathname } from './use-pathname';
2 | export { useRouter } from './use-router';
3 |
--------------------------------------------------------------------------------
/src/routes/hooks/use-pathname.tsx:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react';
2 | import { useLocation } from 'react-router-dom';
3 |
4 | // ----------------------------------------------------------------------
5 |
6 | export function usePathname() {
7 | const { pathname } = useLocation();
8 |
9 | return useMemo(() => pathname, [pathname]);
10 | }
11 |
--------------------------------------------------------------------------------
/src/routes/hooks/use-router.tsx:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react';
2 | import { useNavigate } from 'react-router-dom';
3 |
4 | // ----------------------------------------------------------------------
5 |
6 | export function useRouter() {
7 | const navigate = useNavigate();
8 |
9 | const router = useMemo(
10 | () => ({
11 | back: () => navigate(-1),
12 | forward: () => navigate(1),
13 | reload: () => window.location.reload(),
14 | push: (href: string) => navigate(href),
15 | replace: (href: string) => navigate(href, { replace: true })
16 | }),
17 | [navigate]
18 | );
19 |
20 | return router;
21 | }
22 |
--------------------------------------------------------------------------------
/src/routes/index.tsx:
--------------------------------------------------------------------------------
1 | import FormPage from '@/pages/form';
2 | import NotFound from '@/pages/not-found';
3 | import { Suspense, lazy } from 'react';
4 | import { Navigate, Outlet, useRoutes } from 'react-router-dom';
5 |
6 | const DashboardLayout = lazy(
7 | () => import('@/components/layout/dashboard-layout')
8 | );
9 | const SignInPage = lazy(() => import('@/pages/auth/signin'));
10 | const DashboardPage = lazy(() => import('@/pages/dashboard'));
11 | const StudentPage = lazy(() => import('@/pages/students'));
12 | const StudentDetailPage = lazy(
13 | () => import('@/pages/students/StudentDetailPage')
14 | );
15 |
16 | // ----------------------------------------------------------------------
17 |
18 | export default function AppRouter() {
19 | const dashboardRoutes = [
20 | {
21 | path: '/',
22 | element: (
23 |
24 |
25 |
26 |
27 |
28 | ),
29 | children: [
30 | {
31 | element: ,
32 | index: true
33 | },
34 | {
35 | path: 'student',
36 | element:
37 | },
38 | {
39 | path: 'student/details',
40 | element:
41 | },
42 | {
43 | path: 'form',
44 | element:
45 | }
46 | ]
47 | }
48 | ];
49 |
50 | const publicRoutes = [
51 | {
52 | path: '/login',
53 | element: ,
54 | index: true
55 | },
56 | {
57 | path: '/404',
58 | element:
59 | },
60 | {
61 | path: '*',
62 | element:
63 | }
64 | ];
65 |
66 | const routes = useRoutes([...dashboardRoutes, ...publicRoutes]);
67 |
68 | return routes;
69 | }
70 |
--------------------------------------------------------------------------------
/src/types/index.tsx:
--------------------------------------------------------------------------------
1 | import { Icons } from '@/components/ui/icons';
2 |
3 | export interface NavItem {
4 | title: string;
5 | href: string;
6 | disabled?: boolean;
7 | external?: boolean;
8 | icon?: keyof typeof Icons;
9 | label?: string;
10 | description?: string;
11 | }
12 |
13 | export interface NavItemWithChildren extends NavItem {
14 | items: NavItemWithChildren[];
15 | }
16 |
17 | export interface NavItemWithOptionalChildren extends NavItem {
18 | items?: NavItemWithChildren[];
19 | }
20 |
21 | export interface FooterItem {
22 | title: string;
23 | items: {
24 | title: string;
25 | href: string;
26 | external?: boolean;
27 | }[];
28 | }
29 |
30 | export type MainNavItem = NavItemWithOptionalChildren;
31 |
32 | export type SidebarNavItem = NavItemWithChildren;
33 |
--------------------------------------------------------------------------------
/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 | };
78 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 | "baseUrl": ".",
9 | "paths": {
10 | "@/*": ["./src/*"]
11 | },
12 | /* Bundler mode */
13 | "moduleResolution": "bundler",
14 | "allowImportingTsExtensions": true,
15 | "resolveJsonModule": true,
16 | "isolatedModules": true,
17 | "noEmit": true,
18 | "jsx": "react-jsx",
19 |
20 | /* Linting */
21 | "strict": true,
22 | "noUnusedLocals": true,
23 | "noUnusedParameters": false,
24 | "noFallthroughCasesInSwitch": true,
25 | "noImplicitAny": false
26 | },
27 | "include": ["src"],
28 | "references": [{ "path": "./tsconfig.node.json" }]
29 | }
30 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true,
8 | "strict": true
9 | },
10 | "include": ["vite.config.ts"]
11 | }
12 |
--------------------------------------------------------------------------------
/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "rewrites": [
3 | {
4 | "source": "/(.*)",
5 | "destination": "/index.html"
6 | }
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import react from '@vitejs/plugin-react-swc';
3 | import { defineConfig } from 'vite';
4 |
5 | export default defineConfig({
6 | plugins: [react()],
7 | resolve: {
8 | alias: {
9 | '@': path.resolve(__dirname, './src')
10 | }
11 | }
12 | });
13 |
--------------------------------------------------------------------------------
/vite.config.ts.timestamp-1710421433144-871e35c4a0331.mjs:
--------------------------------------------------------------------------------
1 | // vite.config.ts
2 | import path from 'path';
3 | import react from 'file:///C:/Users/KiraN/Desktop/MyProx/react-shadcn-dashboard/node_modules/@vitejs/plugin-react-swc/index.mjs';
4 | import { defineConfig } from 'file:///C:/Users/KiraN/Desktop/MyProx/react-shadcn-dashboard/node_modules/vite/dist/node/index.js';
5 | var __vite_injected_original_dirname =
6 | 'C:\\Users\\KiraN\\Desktop\\MyProx\\react-shadcn-dashboard';
7 | var vite_config_default = defineConfig({
8 | plugins: [react()],
9 | resolve: {
10 | alias: {
11 | '@': path.resolve(__vite_injected_original_dirname, './src')
12 | }
13 | }
14 | });
15 | export { vite_config_default as default };
16 | //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCJDOlxcXFxVc2Vyc1xcXFxLaXJhTlxcXFxEZXNrdG9wXFxcXE15UHJveFxcXFxyZWFjdC1zaGFkY24tZGFzaGJvYXJkXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ZpbGVuYW1lID0gXCJDOlxcXFxVc2Vyc1xcXFxLaXJhTlxcXFxEZXNrdG9wXFxcXE15UHJveFxcXFxyZWFjdC1zaGFkY24tZGFzaGJvYXJkXFxcXHZpdGUuY29uZmlnLnRzXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ltcG9ydF9tZXRhX3VybCA9IFwiZmlsZTovLy9DOi9Vc2Vycy9LaXJhTi9EZXNrdG9wL015UHJveC9yZWFjdC1zaGFkY24tZGFzaGJvYXJkL3ZpdGUuY29uZmlnLnRzXCI7aW1wb3J0IHBhdGggZnJvbSBcInBhdGhcIjtcbmltcG9ydCByZWFjdCBmcm9tIFwiQHZpdGVqcy9wbHVnaW4tcmVhY3Qtc3djXCI7XG5pbXBvcnQgeyBkZWZpbmVDb25maWcgfSBmcm9tIFwidml0ZVwiO1xuXG5leHBvcnQgZGVmYXVsdCBkZWZpbmVDb25maWcoe1xuICBwbHVnaW5zOiBbcmVhY3QoKV0sXG4gIHJlc29sdmU6IHtcbiAgICBhbGlhczoge1xuICAgICAgXCJAXCI6IHBhdGgucmVzb2x2ZShfX2Rpcm5hbWUsIFwiLi9zcmNcIiksXG4gICAgfSxcbiAgfSxcbn0pO1xuIl0sCiAgIm1hcHBpbmdzIjogIjtBQUEwVixPQUFPLFVBQVU7QUFDM1csT0FBTyxXQUFXO0FBQ2xCLFNBQVMsb0JBQW9CO0FBRjdCLElBQU0sbUNBQW1DO0FBSXpDLElBQU8sc0JBQVEsYUFBYTtBQUFBLEVBQzFCLFNBQVMsQ0FBQyxNQUFNLENBQUM7QUFBQSxFQUNqQixTQUFTO0FBQUEsSUFDUCxPQUFPO0FBQUEsTUFDTCxLQUFLLEtBQUssUUFBUSxrQ0FBVyxPQUFPO0FBQUEsSUFDdEM7QUFBQSxFQUNGO0FBQ0YsQ0FBQzsiLAogICJuYW1lcyI6IFtdCn0K
17 |
--------------------------------------------------------------------------------