diff --git a/.vscode/settings.json b/.vscode/settings.json
index e37cdf0..656c3cb 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,7 +1,11 @@
{
+ // Backend settings
"files.exclude": {
"**/__pycache__/**": true
},
"mypy-type-checker.args": ["--config-file='backend/mypy.ini'"],
- "python.defaultInterpreterPath": "./backend/.venv/bin/python"
+ "python.defaultInterpreterPath": "./backend/.venv/bin/python",
+
+ // Frontend settings
+ "prettier.configPath": "./frontend/.prettierrc"
}
diff --git a/frontend/.prettierrc b/frontend/.prettierrc
new file mode 100644
index 0000000..93b2ed6
--- /dev/null
+++ b/frontend/.prettierrc
@@ -0,0 +1,10 @@
+{
+ "plugins": ["prettier-plugin-tailwindcss"],
+ "proseWrap": "always",
+ "requireConfig": false,
+ "useTabs": true,
+ "trailingComma": "none",
+ "bracketSpacing": true,
+ "jsxBracketSameLine": false,
+ "bracketSameLine": true
+}
\ No newline at end of file
diff --git a/frontend/src/App.css b/frontend/src/App.css
deleted file mode 100644
index b9d355d..0000000
--- a/frontend/src/App.css
+++ /dev/null
@@ -1,42 +0,0 @@
-#root {
- max-width: 1280px;
- margin: 0 auto;
- padding: 2rem;
- text-align: center;
-}
-
-.logo {
- height: 6em;
- padding: 1.5em;
- will-change: filter;
- transition: filter 300ms;
-}
-.logo:hover {
- filter: drop-shadow(0 0 2em #646cffaa);
-}
-.logo.react:hover {
- filter: drop-shadow(0 0 2em #61dafbaa);
-}
-
-@keyframes logo-spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
-}
-
-@media (prefers-reduced-motion: no-preference) {
- a:nth-of-type(2) .logo {
- animation: logo-spin infinite 20s linear;
- }
-}
-
-.card {
- padding: 2em;
-}
-
-.read-the-docs {
- color: #888;
-}
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
deleted file mode 100644
index 1404f34..0000000
--- a/frontend/src/App.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { Button } from "@/components/ui/button";
-import { Card, CardContent } from "@/components/ui/card";
-
-export default function App() {
- return (
-
-
-
- Welcome to My ShadCN Site
- This is a sample site using Vite, TypeScript, and ShadCN UI.
-
-
-
-
- );
-}
diff --git a/frontend/src/components/app-sidebar.tsx b/frontend/src/components/app-sidebar.tsx
index 94ad3a0..572bc4b 100644
--- a/frontend/src/components/app-sidebar.tsx
+++ b/frontend/src/components/app-sidebar.tsx
@@ -1,175 +1,175 @@
-"use client"
+"use client";
-import * as React from "react"
+import * as React from "react";
import {
- AudioWaveform,
- BookOpen,
- Bot,
- Command,
- Frame,
- GalleryVerticalEnd,
- Map,
- PieChart,
- Settings2,
- SquareTerminal,
-} from "lucide-react"
+ AudioWaveform,
+ BookOpen,
+ Bot,
+ Command,
+ Frame,
+ GalleryVerticalEnd,
+ Map,
+ PieChart,
+ Settings2,
+ SquareTerminal
+} from "lucide-react";
-import { NavMain } from "@/components/nav-main"
-import { NavProjects } from "@/components/nav-projects"
-import { NavUser } from "@/components/nav-user"
-import { TeamSwitcher } from "@/components/team-switcher"
+import { NavMain } from "@/components/nav-main";
+import { NavProjects } from "@/components/nav-projects";
+import { NavUser } from "@/components/nav-user";
+import { TeamSwitcher } from "@/components/team-switcher";
import {
- Sidebar,
- SidebarContent,
- SidebarFooter,
- SidebarHeader,
- SidebarRail,
-} from "@/components/ui/sidebar"
+ Sidebar,
+ SidebarContent,
+ SidebarFooter,
+ SidebarHeader,
+ SidebarRail
+} from "@/components/ui/sidebar";
// This is sample data.
const data = {
- user: {
- name: "shadcn",
- email: "m@example.com",
- avatar: "/avatars/shadcn.jpg",
- },
- teams: [
- {
- name: "Acme Inc",
- logo: GalleryVerticalEnd,
- plan: "Enterprise",
- },
- {
- name: "Acme Corp.",
- logo: AudioWaveform,
- plan: "Startup",
- },
- {
- name: "Evil Corp.",
- logo: Command,
- plan: "Free",
- },
- ],
- navMain: [
- {
- title: "Playground",
- url: "#",
- icon: SquareTerminal,
- isActive: true,
- items: [
- {
- title: "History",
- url: "#",
- },
- {
- title: "Starred",
- url: "#",
- },
- {
- title: "Settings",
- url: "#",
- },
- ],
- },
- {
- title: "Models",
- url: "#",
- icon: Bot,
- items: [
- {
- title: "Genesis",
- url: "#",
- },
- {
- title: "Explorer",
- url: "#",
- },
- {
- title: "Quantum",
- url: "#",
- },
- ],
- },
- {
- title: "Documentation",
- url: "#",
- icon: BookOpen,
- items: [
- {
- title: "Introduction",
- url: "#",
- },
- {
- title: "Get Started",
- url: "#",
- },
- {
- title: "Tutorials",
- url: "#",
- },
- {
- title: "Changelog",
- url: "#",
- },
- ],
- },
- {
- title: "Settings",
- url: "#",
- icon: Settings2,
- items: [
- {
- title: "General",
- url: "#",
- },
- {
- title: "Team",
- url: "#",
- },
- {
- title: "Billing",
- url: "#",
- },
- {
- title: "Limits",
- url: "#",
- },
- ],
- },
- ],
- projects: [
- {
- name: "Design Engineering",
- url: "#",
- icon: Frame,
- },
- {
- name: "Sales & Marketing",
- url: "#",
- icon: PieChart,
- },
- {
- name: "Travel",
- url: "#",
- icon: Map,
- },
- ],
-}
+ user: {
+ name: "shadcn",
+ email: "m@example.com",
+ avatar: "/avatars/shadcn.jpg"
+ },
+ teams: [
+ {
+ name: "Acme Inc",
+ logo: GalleryVerticalEnd,
+ plan: "Enterprise"
+ },
+ {
+ name: "Acme Corp.",
+ logo: AudioWaveform,
+ plan: "Startup"
+ },
+ {
+ name: "Evil Corp.",
+ logo: Command,
+ plan: "Free"
+ }
+ ],
+ navMain: [
+ {
+ title: "Playground",
+ url: "#",
+ icon: SquareTerminal,
+ isActive: true,
+ items: [
+ {
+ title: "History",
+ url: "#"
+ },
+ {
+ title: "Starred",
+ url: "#"
+ },
+ {
+ title: "Settings",
+ url: "#"
+ }
+ ]
+ },
+ {
+ title: "Models",
+ url: "#",
+ icon: Bot,
+ items: [
+ {
+ title: "Genesis",
+ url: "#"
+ },
+ {
+ title: "Explorer",
+ url: "#"
+ },
+ {
+ title: "Quantum",
+ url: "#"
+ }
+ ]
+ },
+ {
+ title: "Documentation",
+ url: "#",
+ icon: BookOpen,
+ items: [
+ {
+ title: "Introduction",
+ url: "#"
+ },
+ {
+ title: "Get Started",
+ url: "#"
+ },
+ {
+ title: "Tutorials",
+ url: "#"
+ },
+ {
+ title: "Changelog",
+ url: "#"
+ }
+ ]
+ },
+ {
+ title: "Settings",
+ url: "#",
+ icon: Settings2,
+ items: [
+ {
+ title: "General",
+ url: "#"
+ },
+ {
+ title: "Team",
+ url: "#"
+ },
+ {
+ title: "Billing",
+ url: "#"
+ },
+ {
+ title: "Limits",
+ url: "#"
+ }
+ ]
+ }
+ ],
+ projects: [
+ {
+ name: "Design Engineering",
+ url: "#",
+ icon: Frame
+ },
+ {
+ name: "Sales & Marketing",
+ url: "#",
+ icon: PieChart
+ },
+ {
+ name: "Travel",
+ url: "#",
+ icon: Map
+ }
+ ]
+};
export function AppSidebar({ ...props }: React.ComponentProps) {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
- )
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
}
diff --git a/frontend/src/components/coming-soon.tsx b/frontend/src/components/coming-soon.tsx
index afdfbd5..10afd3d 100644
--- a/frontend/src/components/coming-soon.tsx
+++ b/frontend/src/components/coming-soon.tsx
@@ -1,16 +1,16 @@
-import { IconPlanet } from '@tabler/icons-react'
+import { IconPlanet } from "@tabler/icons-react";
export default function ComingSoon() {
- return (
-
-
-
-
Coming Soon 👀
-
- This page has not been created yet.
- Stay tuned though!
-
-
-
- )
+ return (
+
+
+
+
Coming Soon 👀
+
+ This page has not been created yet.
+ Stay tuned though!
+
+
+
+ );
}
diff --git a/frontend/src/components/command-menu.tsx b/frontend/src/components/command-menu.tsx
index 213795f..fb0c476 100644
--- a/frontend/src/components/command-menu.tsx
+++ b/frontend/src/components/command-menu.tsx
@@ -1,96 +1,94 @@
-import React from 'react'
-import { useNavigate } from '@tanstack/react-router'
+import React from "react";
+import { useNavigate } from "@tanstack/react-router";
import {
- IconArrowRightDashed,
- IconDeviceLaptop,
- IconMoon,
- IconSun,
-} from '@tabler/icons-react'
-import { useSearch } from '@/context/search-context'
-import { useTheme } from '@/context/theme-context'
+ IconArrowRightDashed,
+ IconDeviceLaptop,
+ IconMoon,
+ IconSun
+} from "@tabler/icons-react";
+import { useSearch } from "@/context/search-context";
+import { useTheme } from "@/context/theme-context";
import {
- CommandDialog,
- CommandEmpty,
- CommandGroup,
- CommandInput,
- CommandItem,
- CommandList,
- CommandSeparator,
-} from '@/components/ui/command'
-import { sidebarData } from './layout/data/sidebar-data'
-import { ScrollArea } from './ui/scroll-area'
+ CommandDialog,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+ CommandSeparator
+} from "@/components/ui/command";
+import { sidebarData } from "./layout/data/sidebar-data";
+import { ScrollArea } from "./ui/scroll-area";
export function CommandMenu() {
- const navigate = useNavigate()
- const { setTheme } = useTheme()
- const { open, setOpen } = useSearch()
+ const navigate = useNavigate();
+ const { setTheme } = useTheme();
+ const { open, setOpen } = useSearch();
- const runCommand = React.useCallback(
- (command: () => unknown) => {
- setOpen(false)
- command()
- },
- [setOpen]
- )
+ const runCommand = React.useCallback(
+ (command: () => unknown) => {
+ setOpen(false);
+ command();
+ },
+ [setOpen]
+ );
- return (
-
-
-
-
- No results found.
- {sidebarData.navGroups.map((group) => (
-
- {group.items.map((navItem, i) => {
- if (navItem.url)
- return (
- {
- runCommand(() => navigate({ to: navItem.url }))
- }}
- >
-
-
-
- {navItem.title}
-
- )
+ return (
+
+
+
+
+ No results found.
+ {sidebarData.navGroups.map((group) => (
+
+ {group.items.map((navItem, i) => {
+ if (navItem.url)
+ return (
+ {
+ runCommand(() => navigate({ to: navItem.url }));
+ }}>
+
+
+
+ {navItem.title}
+
+ );
- return navItem.items?.map((subItem, i) => (
- {
- runCommand(() => navigate({ to: subItem.url }))
- }}
- >
-
-
-
- {subItem.title}
-
- ))
- })}
-
- ))}
-
-
- runCommand(() => setTheme('light'))}>
- Light
-
- runCommand(() => setTheme('dark'))}>
-
- Dark
-
- runCommand(() => setTheme('system'))}>
-
- System
-
-
-
-
-
- )
+ return navItem.items?.map((subItem, i) => (
+ {
+ runCommand(() => navigate({ to: subItem.url }));
+ }}>
+
+
+
+ {subItem.title}
+
+ ));
+ })}
+
+ ))}
+
+
+ runCommand(() => setTheme("light"))}>
+ Light
+
+ runCommand(() => setTheme("dark"))}>
+
+ Dark
+
+ runCommand(() => setTheme("system"))}>
+
+ System
+
+
+
+
+
+ );
}
diff --git a/frontend/src/components/confirm-dialog.tsx b/frontend/src/components/confirm-dialog.tsx
index cd81001..e72fd12 100644
--- a/frontend/src/components/confirm-dialog.tsx
+++ b/frontend/src/components/confirm-dialog.tsx
@@ -1,67 +1,66 @@
-import { cn } from '@/lib/utils'
+import { cn } from "@/lib/utils";
import {
- AlertDialog,
- AlertDialogCancel,
- AlertDialogContent,
- AlertDialogDescription,
- AlertDialogFooter,
- AlertDialogHeader,
- AlertDialogTitle,
-} from '@/components/ui/alert-dialog'
-import { Button } from '@/components/ui/button'
+ AlertDialog,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle
+} from "@/components/ui/alert-dialog";
+import { Button } from "@/components/ui/button";
interface ConfirmDialogProps {
- open: boolean
- onOpenChange: (open: boolean) => void
- title: React.ReactNode
- disabled?: boolean
- desc: React.JSX.Element | string
- cancelBtnText?: string
- confirmText?: React.ReactNode
- destructive?: boolean
- handleConfirm: () => void
- isLoading?: boolean
- className?: string
- children?: React.ReactNode
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ title: React.ReactNode;
+ disabled?: boolean;
+ desc: React.JSX.Element | string;
+ cancelBtnText?: string;
+ confirmText?: React.ReactNode;
+ destructive?: boolean;
+ handleConfirm: () => void;
+ isLoading?: boolean;
+ className?: string;
+ children?: React.ReactNode;
}
export function ConfirmDialog(props: ConfirmDialogProps) {
- const {
- title,
- desc,
- children,
- className,
- confirmText,
- cancelBtnText,
- destructive,
- isLoading,
- disabled = false,
- handleConfirm,
- ...actions
- } = props
- return (
-
-
-
- {title}
-
- {desc}
-
-
- {children}
-
-
- {cancelBtnText ?? 'Cancel'}
-
-
-
-
-
- )
+ const {
+ title,
+ desc,
+ children,
+ className,
+ confirmText,
+ cancelBtnText,
+ destructive,
+ isLoading,
+ disabled = false,
+ handleConfirm,
+ ...actions
+ } = props;
+ return (
+
+
+
+ {title}
+
+ {desc}
+
+
+ {children}
+
+
+ {cancelBtnText ?? "Cancel"}
+
+
+
+
+
+ );
}
diff --git a/frontend/src/components/layout/app-sidebar.tsx b/frontend/src/components/layout/app-sidebar.tsx
index 7592e91..44b985e 100644
--- a/frontend/src/components/layout/app-sidebar.tsx
+++ b/frontend/src/components/layout/app-sidebar.tsx
@@ -1,30 +1,30 @@
import {
- Sidebar,
- SidebarContent,
- SidebarFooter,
- SidebarHeader,
- SidebarRail,
-} from '@/components/ui/sidebar'
-import { NavGroup } from '@/components/layout/nav-group'
-import { NavUser } from '@/components/layout/nav-user'
-import { TeamSwitcher } from '@/components/layout/team-switcher'
-import { sidebarData } from './data/sidebar-data'
+ Sidebar,
+ SidebarContent,
+ SidebarFooter,
+ SidebarHeader,
+ SidebarRail
+} from "@/components/ui/sidebar";
+import { NavGroup } from "@/components/layout/nav-group";
+import { NavUser } from "@/components/layout/nav-user";
+import { TeamSwitcher } from "@/components/layout/team-switcher";
+import { sidebarData } from "./data/sidebar-data";
export function AppSidebar({ ...props }: React.ComponentProps) {
- return (
-
-
-
-
-
- {sidebarData.navGroups.map((props) => (
-
- ))}
-
-
-
-
-
-
- )
+ return (
+
+
+
+
+
+ {sidebarData.navGroups.map((props) => (
+
+ ))}
+
+
+
+
+
+
+ );
}
diff --git a/frontend/src/components/layout/data/sidebar-data.ts b/frontend/src/components/layout/data/sidebar-data.ts
index a301ae6..6b6a4b8 100644
--- a/frontend/src/components/layout/data/sidebar-data.ts
+++ b/frontend/src/components/layout/data/sidebar-data.ts
@@ -1,184 +1,189 @@
import {
- IconBarrierBlock,
- IconBrowserCheck,
- IconBug,
- IconChecklist,
- IconError404,
- IconHelp,
- IconLayoutDashboard,
- IconLock,
- IconLockAccess,
- IconMessages,
- IconNotification,
- IconPackages,
- IconPalette,
- IconServerOff,
- IconSettings,
- IconTool,
- IconUserCog,
- IconUserOff,
- IconUsers,
-} from '@tabler/icons-react'
-import { AudioWaveform, Command, GalleryVerticalEnd } from 'lucide-react'
-import { type SidebarData } from '../types'
+ IconBarrierBlock,
+ IconBrowserCheck,
+ IconBug,
+ IconChecklist,
+ IconError404,
+ IconHelp,
+ IconLayoutDashboard,
+ IconLock,
+ IconLockAccess,
+ IconMessages,
+ IconNotification,
+ IconPackages,
+ IconPalette,
+ IconServerOff,
+ IconSettings,
+ IconTool,
+ IconUserCog,
+ IconUserOff,
+ IconUsers
+} from "@tabler/icons-react";
+import { AudioWaveform, Command, GalleryVerticalEnd } from "lucide-react";
+import { type SidebarData } from "../types";
export const sidebarData: SidebarData = {
- user: {
- name: 'satnaing',
- email: 'satnaingdev@gmail.com',
- avatar: '/avatars/shadcn.jpg',
- },
- teams: [
- {
- name: 'Shadcn Admin',
- logo: Command,
- plan: 'Vite + ShadcnUI',
- },
- {
- name: 'Acme Inc',
- logo: GalleryVerticalEnd,
- plan: 'Enterprise',
- },
- {
- name: 'Acme Corp.',
- logo: AudioWaveform,
- plan: 'Startup',
- },
- ],
- navGroups: [
- {
- title: 'General',
- items: [
- {
- title: 'Dashboard',
- url: '/',
- icon: IconLayoutDashboard,
- },
- {
- title: 'Tasks',
- url: '/tasks',
- icon: IconChecklist,
- },
- {
- title: 'Apps',
- url: '/apps',
- icon: IconPackages,
- },
- {
- title: 'Chats',
- url: '/chats',
- badge: '3',
- icon: IconMessages,
- },
- {
- title: 'Users',
- url: '/users',
- icon: IconUsers,
- },
- ],
- },
- {
- title: 'Pages',
- items: [
- {
- title: 'Auth',
- icon: IconLockAccess,
- items: [
- {
- title: 'Sign In',
- url: '/sign-in',
- },
- {
- title: 'Sign In (2 Col)',
- url: '/sign-in-2',
- },
- {
- title: 'Sign Up',
- url: '/sign-up',
- },
- {
- title: 'Forgot Password',
- url: '/forgot-password',
- },
- {
- title: 'OTP',
- url: '/otp',
- },
- ],
- },
- {
- title: 'Errors',
- icon: IconBug,
- items: [
- {
- title: 'Unauthorized',
- url: '/401',
- icon: IconLock,
- },
- {
- title: 'Forbidden',
- url: '/403',
- icon: IconUserOff,
- },
- {
- title: 'Not Found',
- url: '/404',
- icon: IconError404,
- },
- {
- title: 'Internal Server Error',
- url: '/500',
- icon: IconServerOff,
- },
- {
- title: 'Maintenance Error',
- url: '/503',
- icon: IconBarrierBlock,
- },
- ],
- },
- ],
- },
- {
- title: 'Other',
- items: [
- {
- title: 'Settings',
- icon: IconSettings,
- items: [
- {
- title: 'Profile',
- url: '/settings',
- icon: IconUserCog,
- },
- {
- title: 'Account',
- url: '/settings/account',
- icon: IconTool,
- },
- {
- title: 'Appearance',
- url: '/settings/appearance',
- icon: IconPalette,
- },
- {
- title: 'Notifications',
- url: '/settings/notifications',
- icon: IconNotification,
- },
- {
- title: 'Display',
- url: '/settings/display',
- icon: IconBrowserCheck,
- },
- ],
- },
- {
- title: 'Help Center',
- url: '/help-center',
- icon: IconHelp,
- },
- ],
- },
- ],
-}
+ user: {
+ name: "satnaing",
+ email: "satnaingdev@gmail.com",
+ avatar: "/avatars/shadcn.jpg"
+ },
+ teams: [
+ {
+ name: "Shadcn Admin",
+ logo: Command,
+ plan: "Vite + ShadcnUI"
+ },
+ {
+ name: "Acme Inc",
+ logo: GalleryVerticalEnd,
+ plan: "Enterprise"
+ },
+ {
+ name: "Acme Corp.",
+ logo: AudioWaveform,
+ plan: "Startup"
+ }
+ ],
+ navGroups: [
+ {
+ title: "General",
+ items: [
+ {
+ title: "Dashboard",
+ url: "/",
+ icon: IconLayoutDashboard
+ },
+ {
+ title: "Tasks",
+ url: "/tasks",
+ icon: IconChecklist
+ },
+ {
+ title: "Apps",
+ url: "/apps",
+ icon: IconPackages
+ },
+ {
+ title: "Chats",
+ url: "/chats",
+ badge: "3",
+ icon: IconMessages
+ },
+ {
+ title: "Users",
+ url: "/users",
+ icon: IconUsers
+ },
+ {
+ title: "NBA",
+ url: "/nba",
+ icon: IconChecklist
+ }
+ ]
+ },
+ {
+ title: "Pages",
+ items: [
+ {
+ title: "Auth",
+ icon: IconLockAccess,
+ items: [
+ {
+ title: "Sign In",
+ url: "/sign-in"
+ },
+ {
+ title: "Sign In (2 Col)",
+ url: "/sign-in-2"
+ },
+ {
+ title: "Sign Up",
+ url: "/sign-up"
+ },
+ {
+ title: "Forgot Password",
+ url: "/forgot-password"
+ },
+ {
+ title: "OTP",
+ url: "/otp"
+ }
+ ]
+ },
+ {
+ title: "Errors",
+ icon: IconBug,
+ items: [
+ {
+ title: "Unauthorized",
+ url: "/401",
+ icon: IconLock
+ },
+ {
+ title: "Forbidden",
+ url: "/403",
+ icon: IconUserOff
+ },
+ {
+ title: "Not Found",
+ url: "/404",
+ icon: IconError404
+ },
+ {
+ title: "Internal Server Error",
+ url: "/500",
+ icon: IconServerOff
+ },
+ {
+ title: "Maintenance Error",
+ url: "/503",
+ icon: IconBarrierBlock
+ }
+ ]
+ }
+ ]
+ },
+ {
+ title: "Other",
+ items: [
+ {
+ title: "Settings",
+ icon: IconSettings,
+ items: [
+ {
+ title: "Profile",
+ url: "/settings",
+ icon: IconUserCog
+ },
+ {
+ title: "Account",
+ url: "/settings/account",
+ icon: IconTool
+ },
+ {
+ title: "Appearance",
+ url: "/settings/appearance",
+ icon: IconPalette
+ },
+ {
+ title: "Notifications",
+ url: "/settings/notifications",
+ icon: IconNotification
+ },
+ {
+ title: "Display",
+ url: "/settings/display",
+ icon: IconBrowserCheck
+ }
+ ]
+ },
+ {
+ title: "Help Center",
+ url: "/help-center",
+ icon: IconHelp
+ }
+ ]
+ }
+ ]
+};
diff --git a/frontend/src/components/layout/header.tsx b/frontend/src/components/layout/header.tsx
index 950ee2f..12a84b2 100644
--- a/frontend/src/components/layout/header.tsx
+++ b/frontend/src/components/layout/header.tsx
@@ -1,48 +1,47 @@
-import React from 'react'
-import { cn } from '@/lib/utils'
-import { Separator } from '@/components/ui/separator'
-import { SidebarTrigger } from '@/components/ui/sidebar'
+import React from "react";
+import { cn } from "@/lib/utils";
+import { Separator } from "@/components/ui/separator";
+import { SidebarTrigger } from "@/components/ui/sidebar";
interface HeaderProps extends React.HTMLAttributes {
- fixed?: boolean
- ref?: React.Ref
+ fixed?: boolean;
+ ref?: React.Ref;
}
export const Header = ({
- className,
- fixed,
- children,
- ...props
+ className,
+ fixed,
+ children,
+ ...props
}: HeaderProps) => {
- const [offset, setOffset] = React.useState(0)
+ const [offset, setOffset] = React.useState(0);
- React.useEffect(() => {
- const onScroll = () => {
- setOffset(document.body.scrollTop || document.documentElement.scrollTop)
- }
+ React.useEffect(() => {
+ const onScroll = () => {
+ setOffset(document.body.scrollTop || document.documentElement.scrollTop);
+ };
- // Add scroll listener to the body
- document.addEventListener('scroll', onScroll, { passive: true })
+ // Add scroll listener to the body
+ document.addEventListener("scroll", onScroll, { passive: true });
- // Clean up the event listener on unmount
- return () => document.removeEventListener('scroll', onScroll)
- }, [])
+ // Clean up the event listener on unmount
+ return () => document.removeEventListener("scroll", onScroll);
+ }, []);
- return (
- 10 && fixed ? 'shadow' : 'shadow-none',
- className
- )}
- {...props}
- >
-
-
- {children}
-
- )
-}
+ return (
+ 10 && fixed ? "shadow" : "shadow-none",
+ className
+ )}
+ {...props}>
+
+
+ {children}
+
+ );
+};
-Header.displayName = 'Header'
+Header.displayName = "Header";
diff --git a/frontend/src/components/layout/main.tsx b/frontend/src/components/layout/main.tsx
index 8d9cdd3..cd7ed34 100644
--- a/frontend/src/components/layout/main.tsx
+++ b/frontend/src/components/layout/main.tsx
@@ -1,22 +1,22 @@
-import React from 'react'
-import { cn } from '@/lib/utils'
+import React from "react";
+import { cn } from "@/lib/utils";
interface MainProps extends React.HTMLAttributes {
- fixed?: boolean
- ref?: React.Ref
+ fixed?: boolean;
+ ref?: React.Ref;
}
export const Main = ({ fixed, ...props }: MainProps) => {
- return (
-
- )
-}
+ return (
+
+ );
+};
-Main.displayName = 'Main'
+Main.displayName = "Main";
diff --git a/frontend/src/components/layout/nav-group.tsx b/frontend/src/components/layout/nav-group.tsx
index bdaa208..40fd93e 100644
--- a/frontend/src/components/layout/nav-group.tsx
+++ b/frontend/src/components/layout/nav-group.tsx
@@ -1,180 +1,175 @@
-import { ReactNode } from 'react'
-import { Link, useLocation } from '@tanstack/react-router'
-import { ChevronRight } from 'lucide-react'
+import { ReactNode } from "react";
+import { Link, useLocation } from "@tanstack/react-router";
+import { ChevronRight } from "lucide-react";
import {
- Collapsible,
- CollapsibleContent,
- CollapsibleTrigger,
-} from '@/components/ui/collapsible'
+ Collapsible,
+ CollapsibleContent,
+ CollapsibleTrigger
+} from "@/components/ui/collapsible";
import {
- SidebarGroup,
- SidebarGroupLabel,
- SidebarMenu,
- SidebarMenuButton,
- SidebarMenuItem,
- SidebarMenuSub,
- SidebarMenuSubButton,
- SidebarMenuSubItem,
- useSidebar,
-} from '@/components/ui/sidebar'
-import { Badge } from '../ui/badge'
+ SidebarGroup,
+ SidebarGroupLabel,
+ SidebarMenu,
+ SidebarMenuButton,
+ SidebarMenuItem,
+ SidebarMenuSub,
+ SidebarMenuSubButton,
+ SidebarMenuSubItem,
+ useSidebar
+} from "@/components/ui/sidebar";
+import { Badge } from "../ui/badge";
import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuLabel,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from '../ui/dropdown-menu'
-import { NavCollapsible, NavItem, NavLink, type NavGroup } from './types'
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger
+} from "../ui/dropdown-menu";
+import { NavCollapsible, NavItem, NavLink, type NavGroup } from "./types";
export function NavGroup({ title, items }: NavGroup) {
- const { state } = useSidebar()
- const href = useLocation({ select: (location) => location.href })
- return (
-
- {title}
-
- {items.map((item) => {
- const key = `${item.title}-${item.url}`
+ const { state } = useSidebar();
+ const href = useLocation({ select: (location) => location.href });
+ return (
+
+ {title}
+
+ {items.map((item) => {
+ const key = `${item.title}-${item.url}`;
- if (!item.items)
- return
+ if (!item.items)
+ return ;
- if (state === 'collapsed')
- return (
-
- )
+ if (state === "collapsed")
+ return (
+
+ );
- return
- })}
-
-
- )
+ return ;
+ })}
+
+
+ );
}
const NavBadge = ({ children }: { children: ReactNode }) => (
- {children}
-)
+ {children}
+);
const SidebarMenuLink = ({ item, href }: { item: NavLink; href: string }) => {
- const { setOpenMobile } = useSidebar()
- return (
-
-
- setOpenMobile(false)}>
- {item.icon && }
- {item.title}
- {item.badge && {item.badge}}
-
-
-
- )
-}
+ const { setOpenMobile } = useSidebar();
+ return (
+
+
+ setOpenMobile(false)}>
+ {item.icon && }
+ {item.title}
+ {item.badge && {item.badge}}
+
+
+
+ );
+};
const SidebarMenuCollapsible = ({
- item,
- href,
+ item,
+ href
}: {
- item: NavCollapsible
- href: string
+ item: NavCollapsible;
+ href: string;
}) => {
- const { setOpenMobile } = useSidebar()
- return (
-
-
-
-
- {item.icon && }
- {item.title}
- {item.badge && {item.badge}}
-
-
-
-
-
- {item.items.map((subItem) => (
-
-
- setOpenMobile(false)}>
- {subItem.icon && }
- {subItem.title}
- {subItem.badge && {subItem.badge}}
-
-
-
- ))}
-
-
-
-
- )
-}
+ const { setOpenMobile } = useSidebar();
+ return (
+
+
+
+
+ {item.icon && }
+ {item.title}
+ {item.badge && {item.badge}}
+
+
+
+
+
+ {item.items.map((subItem) => (
+
+
+ setOpenMobile(false)}>
+ {subItem.icon && }
+ {subItem.title}
+ {subItem.badge && {subItem.badge}}
+
+
+
+ ))}
+
+
+
+
+ );
+};
const SidebarMenuCollapsedDropdown = ({
- item,
- href,
+ item,
+ href
}: {
- item: NavCollapsible
- href: string
+ item: NavCollapsible;
+ href: string;
}) => {
- return (
-
-
-
-
- {item.icon && }
- {item.title}
- {item.badge && {item.badge}}
-
-
-
-
-
- {item.title} {item.badge ? `(${item.badge})` : ''}
-
-
- {item.items.map((sub) => (
-
-
- {sub.icon && }
- {sub.title}
- {sub.badge && (
- {sub.badge}
- )}
-
-
- ))}
-
-
-
- )
-}
+ return (
+
+
+
+
+ {item.icon && }
+ {item.title}
+ {item.badge && {item.badge}}
+
+
+
+
+
+ {item.title} {item.badge ? `(${item.badge})` : ""}
+
+
+ {item.items.map((sub) => (
+
+
+ {sub.icon && }
+ {sub.title}
+ {sub.badge && (
+ {sub.badge}
+ )}
+
+
+ ))}
+
+
+
+ );
+};
function checkIsActive(href: string, item: NavItem, mainNav = false) {
- return (
- href === item.url || // /endpint?search=param
- href.split('?')[0] === item.url || // endpoint
- !!item?.items?.filter((i) => i.url === href).length || // if child nav is active
- (mainNav &&
- href.split('/')[1] !== '' &&
- href.split('/')[1] === item?.url?.split('/')[1])
- )
+ return (
+ href === item.url || // /endpint?search=param
+ href.split("?")[0] === item.url || // endpoint
+ !!item?.items?.filter((i) => i.url === href).length || // if child nav is active
+ (mainNav &&
+ href.split("/")[1] !== "" &&
+ href.split("/")[1] === item?.url?.split("/")[1])
+ );
}
diff --git a/frontend/src/components/layout/nav-user.tsx b/frontend/src/components/layout/nav-user.tsx
index 6391f33..d340699 100644
--- a/frontend/src/components/layout/nav-user.tsx
+++ b/frontend/src/components/layout/nav-user.tsx
@@ -1,114 +1,112 @@
-import { Link } from '@tanstack/react-router'
+import { Link } from "@tanstack/react-router";
import {
- BadgeCheck,
- Bell,
- ChevronsUpDown,
- CreditCard,
- LogOut,
- Sparkles,
-} from 'lucide-react'
-import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
+ BadgeCheck,
+ Bell,
+ ChevronsUpDown,
+ CreditCard,
+ LogOut,
+ Sparkles
+} from "lucide-react";
+import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuGroup,
- DropdownMenuItem,
- DropdownMenuLabel,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from '@/components/ui/dropdown-menu'
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger
+} from "@/components/ui/dropdown-menu";
import {
- SidebarMenu,
- SidebarMenuButton,
- SidebarMenuItem,
- useSidebar,
-} from '@/components/ui/sidebar'
+ SidebarMenu,
+ SidebarMenuButton,
+ SidebarMenuItem,
+ useSidebar
+} from "@/components/ui/sidebar";
export function NavUser({
- user,
+ user
}: {
- user: {
- name: string
- email: string
- avatar: string
- }
+ user: {
+ name: string;
+ email: string;
+ avatar: string;
+ };
}) {
- const { isMobile } = useSidebar()
+ const { isMobile } = useSidebar();
- return (
-
-
-
-
-
-
-
- SN
-
-
- {user.name}
- {user.email}
-
-
-
-
-
-
-
-
-
- SN
-
-
- {user.name}
- {user.email}
-
-
-
-
-
-
-
- Upgrade to Pro
-
-
-
-
-
-
-
- Account
-
-
-
-
-
- Billing
-
-
-
-
-
- Notifications
-
-
-
-
-
-
- Log out
-
-
-
-
-
- )
+ return (
+
+
+
+
+
+
+
+ SN
+
+
+ {user.name}
+ {user.email}
+
+
+
+
+
+
+
+
+
+ SN
+
+
+ {user.name}
+ {user.email}
+
+
+
+
+
+
+
+ Upgrade to Pro
+
+
+
+
+
+
+
+ Account
+
+
+
+
+
+ Billing
+
+
+
+
+
+ Notifications
+
+
+
+
+
+
+ Log out
+
+
+
+
+
+ );
}
diff --git a/frontend/src/components/layout/team-switcher.tsx b/frontend/src/components/layout/team-switcher.tsx
index df1e345..1eb944e 100644
--- a/frontend/src/components/layout/team-switcher.tsx
+++ b/frontend/src/components/layout/team-switcher.tsx
@@ -1,86 +1,83 @@
-import * as React from 'react'
-import { ChevronsUpDown, Plus } from 'lucide-react'
+import * as React from "react";
+import { ChevronsUpDown, Plus } from "lucide-react";
import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuLabel,
- DropdownMenuSeparator,
- DropdownMenuShortcut,
- DropdownMenuTrigger,
-} from '@/components/ui/dropdown-menu'
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuTrigger
+} from "@/components/ui/dropdown-menu";
import {
- SidebarMenu,
- SidebarMenuButton,
- SidebarMenuItem,
- useSidebar,
-} from '@/components/ui/sidebar'
+ SidebarMenu,
+ SidebarMenuButton,
+ SidebarMenuItem,
+ useSidebar
+} from "@/components/ui/sidebar";
export function TeamSwitcher({
- teams,
+ teams
}: {
- teams: {
- name: string
- logo: React.ElementType
- plan: string
- }[]
+ teams: {
+ name: string;
+ logo: React.ElementType;
+ plan: string;
+ }[];
}) {
- const { isMobile } = useSidebar()
- const [activeTeam, setActiveTeam] = React.useState(teams[0])
+ const { isMobile } = useSidebar();
+ const [activeTeam, setActiveTeam] = React.useState(teams[0]);
- return (
-
-
-
-
-
-
-
-
- {activeTeam.name}
-
- {activeTeam.plan}
-
-
-
-
-
-
- Teams
-
- {teams.map((team, index) => (
- setActiveTeam(team)}
- className='gap-2 p-2'
- >
-
-
-
- {team.name}
- ⌘{index + 1}
-
- ))}
-
-
-
- Add team
-
-
-
-
-
- )
+ return (
+
+
+
+
+
+
+
+
+ {activeTeam.name}
+
+ {activeTeam.plan}
+
+
+
+
+
+
+ Teams
+
+ {teams.map((team, index) => (
+ setActiveTeam(team)}
+ className="gap-2 p-2">
+
+
+
+ {team.name}
+ ⌘{index + 1}
+
+ ))}
+
+
+
+ Add team
+
+
+
+
+
+ );
}
diff --git a/frontend/src/components/layout/top-nav.tsx b/frontend/src/components/layout/top-nav.tsx
index 29d7316..bda0805 100644
--- a/frontend/src/components/layout/top-nav.tsx
+++ b/frontend/src/components/layout/top-nav.tsx
@@ -1,67 +1,64 @@
-import { Link } from '@tanstack/react-router'
-import { IconMenu } from '@tabler/icons-react'
-import { cn } from '@/lib/utils'
-import { Button } from '@/components/ui/button'
+import { Link } from "@tanstack/react-router";
+import { IconMenu } from "@tabler/icons-react";
+import { cn } from "@/lib/utils";
+import { Button } from "@/components/ui/button";
import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuTrigger,
-} from '@/components/ui/dropdown-menu'
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger
+} from "@/components/ui/dropdown-menu";
interface TopNavProps extends React.HTMLAttributes {
- links: {
- title: string
- href: string
- isActive: boolean
- disabled?: boolean
- }[]
+ links: {
+ title: string;
+ href: string;
+ isActive: boolean;
+ disabled?: boolean;
+ }[];
}
export function TopNav({ className, links, ...props }: TopNavProps) {
- return (
- <>
-
-
-
-
-
-
- {links.map(({ title, href, isActive, disabled }) => (
-
-
- {title}
-
-
- ))}
-
-
-
+ return (
+ <>
+
+
+
+
+
+
+ {links.map(({ title, href, isActive, disabled }) => (
+
+
+ {title}
+
+
+ ))}
+
+
+
-
- >
- )
+
+ >
+ );
}
diff --git a/frontend/src/components/layout/types.ts b/frontend/src/components/layout/types.ts
index 86e8f6e..235b707 100644
--- a/frontend/src/components/layout/types.ts
+++ b/frontend/src/components/layout/types.ts
@@ -1,44 +1,44 @@
-import { LinkProps } from '@tanstack/react-router'
+import { LinkProps } from "@tanstack/react-router";
interface User {
- name: string
- email: string
- avatar: string
+ name: string;
+ email: string;
+ avatar: string;
}
interface Team {
- name: string
- logo: React.ElementType
- plan: string
+ name: string;
+ logo: React.ElementType;
+ plan: string;
}
interface BaseNavItem {
- title: string
- badge?: string
- icon?: React.ElementType
+ title: string;
+ badge?: string;
+ icon?: React.ElementType;
}
type NavLink = BaseNavItem & {
- url: LinkProps['to']
- items?: never
-}
+ url: LinkProps["to"];
+ items?: never;
+};
type NavCollapsible = BaseNavItem & {
- items: (BaseNavItem & { url: LinkProps['to'] })[]
- url?: never
-}
+ items: (BaseNavItem & { url: LinkProps["to"] })[];
+ url?: never;
+};
-type NavItem = NavCollapsible | NavLink
+type NavItem = NavCollapsible | NavLink;
interface NavGroup {
- title: string
- items: NavItem[]
+ title: string;
+ items: NavItem[];
}
interface SidebarData {
- user: User
- teams: Team[]
- navGroups: NavGroup[]
+ user: User;
+ teams: Team[];
+ navGroups: NavGroup[];
}
-export type { SidebarData, NavGroup, NavItem, NavCollapsible, NavLink }
+export type { SidebarData, NavGroup, NavItem, NavCollapsible, NavLink };
diff --git a/frontend/src/components/long-text.tsx b/frontend/src/components/long-text.tsx
index 5f9fa07..1b675d4 100644
--- a/frontend/src/components/long-text.tsx
+++ b/frontend/src/components/long-text.tsx
@@ -1,85 +1,85 @@
-import { useEffect, useRef, useState } from 'react'
-import { cn } from '@/lib/utils'
+import { useEffect, useRef, useState } from "react";
+import { cn } from "@/lib/utils";
import {
- Popover,
- PopoverContent,
- PopoverTrigger,
-} from '@/components/ui/popover'
+ Popover,
+ PopoverContent,
+ PopoverTrigger
+} from "@/components/ui/popover";
import {
- Tooltip,
- TooltipContent,
- TooltipProvider,
- TooltipTrigger,
-} from '@/components/ui/tooltip'
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger
+} from "@/components/ui/tooltip";
interface Props {
- children: React.ReactNode
- className?: string
- contentClassName?: string
+ children: React.ReactNode;
+ className?: string;
+ contentClassName?: string;
}
export default function LongText({
- children,
- className = '',
- contentClassName = '',
+ children,
+ className = "",
+ contentClassName = ""
}: Props) {
- const ref = useRef(null)
- const [isOverflown, setIsOverflown] = useState(false)
+ const ref = useRef(null);
+ const [isOverflown, setIsOverflown] = useState(false);
- useEffect(() => {
- if (checkOverflow(ref.current)) {
- setIsOverflown(true)
- return
- }
+ useEffect(() => {
+ if (checkOverflow(ref.current)) {
+ setIsOverflown(true);
+ return;
+ }
- setIsOverflown(false)
- }, [])
+ setIsOverflown(false);
+ }, []);
- if (!isOverflown)
- return (
-
- {children}
-
- )
+ if (!isOverflown)
+ return (
+
+ {children}
+
+ );
- return (
- <>
-
-
-
-
-
- {children}
-
-
-
- {children}
-
-
-
-
-
-
-
-
- {children}
-
-
-
- {children}
-
-
-
- >
- )
+ return (
+ <>
+
+
+
+
+
+ {children}
+
+
+
+ {children}
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+ {children}
+
+
+
+ >
+ );
}
const checkOverflow = (textContainer: HTMLDivElement | null) => {
- if (textContainer) {
- return (
- textContainer.offsetHeight < textContainer.scrollHeight ||
- textContainer.offsetWidth < textContainer.scrollWidth
- )
- }
- return false
-}
+ if (textContainer) {
+ return (
+ textContainer.offsetHeight < textContainer.scrollHeight ||
+ textContainer.offsetWidth < textContainer.scrollWidth
+ );
+ }
+ return false;
+};
diff --git a/frontend/src/components/nav-main.tsx b/frontend/src/components/nav-main.tsx
index 1d71af1..c6c95c4 100644
--- a/frontend/src/components/nav-main.tsx
+++ b/frontend/src/components/nav-main.tsx
@@ -1,73 +1,72 @@
-"use client"
+"use client";
-import { ChevronRight, type LucideIcon } from "lucide-react"
+import { ChevronRight, type LucideIcon } from "lucide-react";
import {
- Collapsible,
- CollapsibleContent,
- CollapsibleTrigger,
-} from "@/components/ui/collapsible"
+ Collapsible,
+ CollapsibleContent,
+ CollapsibleTrigger
+} from "@/components/ui/collapsible";
import {
- SidebarGroup,
- SidebarGroupLabel,
- SidebarMenu,
- SidebarMenuButton,
- SidebarMenuItem,
- SidebarMenuSub,
- SidebarMenuSubButton,
- SidebarMenuSubItem,
-} from "@/components/ui/sidebar"
+ SidebarGroup,
+ SidebarGroupLabel,
+ SidebarMenu,
+ SidebarMenuButton,
+ SidebarMenuItem,
+ SidebarMenuSub,
+ SidebarMenuSubButton,
+ SidebarMenuSubItem
+} from "@/components/ui/sidebar";
export function NavMain({
- items,
+ items
}: {
- items: {
- title: string
- url: string
- icon?: LucideIcon
- isActive?: boolean
- items?: {
- title: string
- url: string
- }[]
- }[]
+ items: {
+ title: string;
+ url: string;
+ icon?: LucideIcon;
+ isActive?: boolean;
+ items?: {
+ title: string;
+ url: string;
+ }[];
+ }[];
}) {
- return (
-
- Platform
-
- {items.map((item) => (
-
-
-
-
- {item.icon && }
- {item.title}
-
-
-
-
-
- {item.items?.map((subItem) => (
-
-
-
- {subItem.title}
-
-
-
- ))}
-
-
-
-
- ))}
-
-
- )
+ return (
+
+ Platform
+
+ {items.map((item) => (
+
+
+
+
+ {item.icon && }
+ {item.title}
+
+
+
+
+
+ {item.items?.map((subItem) => (
+
+
+
+ {subItem.title}
+
+
+
+ ))}
+
+
+
+
+ ))}
+
+
+ );
}
diff --git a/frontend/src/components/nav-projects.tsx b/frontend/src/components/nav-projects.tsx
index f50b20d..1ce36e9 100644
--- a/frontend/src/components/nav-projects.tsx
+++ b/frontend/src/components/nav-projects.tsx
@@ -1,89 +1,88 @@
-"use client"
+"use client";
import {
- Folder,
- Forward,
- MoreHorizontal,
- Trash2,
- type LucideIcon,
-} from "lucide-react"
+ Folder,
+ Forward,
+ MoreHorizontal,
+ Trash2,
+ type LucideIcon
+} from "lucide-react";
import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu"
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger
+} from "@/components/ui/dropdown-menu";
import {
- SidebarGroup,
- SidebarGroupLabel,
- SidebarMenu,
- SidebarMenuAction,
- SidebarMenuButton,
- SidebarMenuItem,
- useSidebar,
-} from "@/components/ui/sidebar"
+ SidebarGroup,
+ SidebarGroupLabel,
+ SidebarMenu,
+ SidebarMenuAction,
+ SidebarMenuButton,
+ SidebarMenuItem,
+ useSidebar
+} from "@/components/ui/sidebar";
export function NavProjects({
- projects,
+ projects
}: {
- projects: {
- name: string
- url: string
- icon: LucideIcon
- }[]
+ projects: {
+ name: string;
+ url: string;
+ icon: LucideIcon;
+ }[];
}) {
- const { isMobile } = useSidebar()
+ const { isMobile } = useSidebar();
- return (
-
- Projects
-
- {projects.map((item) => (
-
-
-
-
- {item.name}
-
-
-
-
-
-
- More
-
-
-
-
-
- View Project
-
-
-
- Share Project
-
-
-
-
- Delete Project
-
-
-
-
- ))}
-
-
-
- More
-
-
-
-
- )
+ return (
+
+ Projects
+
+ {projects.map((item) => (
+
+
+
+
+ {item.name}
+
+
+
+
+
+
+ More
+
+
+
+
+
+ View Project
+
+
+
+ Share Project
+
+
+
+
+ Delete Project
+
+
+
+
+ ))}
+
+
+
+ More
+
+
+
+
+ );
}
diff --git a/frontend/src/components/nav-user.tsx b/frontend/src/components/nav-user.tsx
index d12ef78..234d7ec 100644
--- a/frontend/src/components/nav-user.tsx
+++ b/frontend/src/components/nav-user.tsx
@@ -1,114 +1,108 @@
-"use client"
+"use client";
import {
- BadgeCheck,
- Bell,
- ChevronsUpDown,
- CreditCard,
- LogOut,
- Sparkles,
-} from "lucide-react"
+ BadgeCheck,
+ Bell,
+ ChevronsUpDown,
+ CreditCard,
+ LogOut,
+ Sparkles
+} from "lucide-react";
+import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import {
- Avatar,
- AvatarFallback,
- AvatarImage,
-} from "@/components/ui/avatar"
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger
+} from "@/components/ui/dropdown-menu";
import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuGroup,
- DropdownMenuItem,
- DropdownMenuLabel,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu"
-import {
- SidebarMenu,
- SidebarMenuButton,
- SidebarMenuItem,
- useSidebar,
-} from "@/components/ui/sidebar"
+ SidebarMenu,
+ SidebarMenuButton,
+ SidebarMenuItem,
+ useSidebar
+} from "@/components/ui/sidebar";
export function NavUser({
- user,
+ user
}: {
- user: {
- name: string
- email: string
- avatar: string
- }
+ user: {
+ name: string;
+ email: string;
+ avatar: string;
+ };
}) {
- const { isMobile } = useSidebar()
+ const { isMobile } = useSidebar();
- return (
-
-
-
-
-
-
-
- CN
-
-
- {user.name}
- {user.email}
-
-
-
-
-
-
-
-
-
- CN
-
-
- {user.name}
- {user.email}
-
-
-
-
-
-
-
- Upgrade to Pro
-
-
-
-
-
-
- Account
-
-
-
- Billing
-
-
-
- Notifications
-
-
-
-
-
- Log out
-
-
-
-
-
- )
+ return (
+
+
+
+
+
+
+
+ CN
+
+
+ {user.name}
+ {user.email}
+
+
+
+
+
+
+
+
+
+ CN
+
+
+ {user.name}
+ {user.email}
+
+
+
+
+
+
+
+ Upgrade to Pro
+
+
+
+
+
+
+ Account
+
+
+
+ Billing
+
+
+
+ Notifications
+
+
+
+
+
+ Log out
+
+
+
+
+
+ );
}
diff --git a/frontend/src/components/password-input.tsx b/frontend/src/components/password-input.tsx
index e5afeb8..93c6fc8 100644
--- a/frontend/src/components/password-input.tsx
+++ b/frontend/src/components/password-input.tsx
@@ -1,39 +1,38 @@
-import * as React from 'react'
-import { IconEye, IconEyeOff } from '@tabler/icons-react'
-import { cn } from '@/lib/utils'
-import { Button } from './ui/button'
+import * as React from "react";
+import { IconEye, IconEyeOff } from "@tabler/icons-react";
+import { cn } from "@/lib/utils";
+import { Button } from "./ui/button";
type PasswordInputProps = Omit<
- React.InputHTMLAttributes,
- 'type'
->
+ React.InputHTMLAttributes,
+ "type"
+>;
const PasswordInput = React.forwardRef(
- ({ className, disabled, ...props }, ref) => {
- const [showPassword, setShowPassword] = React.useState(false)
- return (
-
-
-
-
- )
- }
-)
-PasswordInput.displayName = 'PasswordInput'
+ ({ className, disabled, ...props }, ref) => {
+ const [showPassword, setShowPassword] = React.useState(false);
+ return (
+
+
+
+
+ );
+ }
+);
+PasswordInput.displayName = "PasswordInput";
-export { PasswordInput }
+export { PasswordInput };
diff --git a/frontend/src/components/pin-input.tsx b/frontend/src/components/pin-input.tsx
index 7356d68..d811d20 100644
--- a/frontend/src/components/pin-input.tsx
+++ b/frontend/src/components/pin-input.tsx
@@ -1,454 +1,454 @@
-import * as React from 'react'
-import { cn } from '@/lib/utils'
+import * as React from "react";
+import { cn } from "@/lib/utils";
interface PinInputProps {
- children:
- | React.ReactElement
- | React.ReactElement[]
- /**
- * className for the input container
- */
- className?: string
- /**
- * `aria-label` for the input fields
- */
- ariaLabel?: string
- /**
- * If set, the pin input receives focus on mount, `false` by default
- */
- autoFocus?: boolean
- /**
- * Called when value changes
- */
- onChange?: (value: string) => void
- /**
- * Called when all inputs have valid value
- */
- onComplete?: (value: string) => void
- /**
- * Called when any input doesn't have value
- */
- onIncomplete?: (value: string) => void
- /**
- * `name` attribute for input fields
- */
- name?: string
- /**
- * `form` attribute for hidden input
- */
- form?: string
- /**
- * If set, the input's value will be masked just like password input. This field is `false` by default
- */
- mask?: boolean
- /**
- * If set, the pin input component signals to its fields that they should
- * use `autocomplete="one-time-code"`. This field is `false` by default
- */
- otp?: boolean
- /**
- * Uncontrolled pin input default value.
- */
- defaultValue?: string
- /**
- * Controlled pin input value.
- */
- value?: string
- /**
- * The type of value pin input should allow, `alphanumeric` by default
- */
- type?: 'numeric' | 'alphanumeric'
- /**
- * Placeholder for input fields, `â—‹` by default
- */
- placeholder?: string
- /**
- * If set, the user cannot set the value, `false` by default
- */
- readOnly?: boolean
- /**
- * If set, the input fields are disabled, `false` by default
- */
- disabled?: boolean
- ref?: React.Ref
+ children:
+ | React.ReactElement
+ | React.ReactElement[];
+ /**
+ * className for the input container
+ */
+ className?: string;
+ /**
+ * `aria-label` for the input fields
+ */
+ ariaLabel?: string;
+ /**
+ * If set, the pin input receives focus on mount, `false` by default
+ */
+ autoFocus?: boolean;
+ /**
+ * Called when value changes
+ */
+ onChange?: (value: string) => void;
+ /**
+ * Called when all inputs have valid value
+ */
+ onComplete?: (value: string) => void;
+ /**
+ * Called when any input doesn't have value
+ */
+ onIncomplete?: (value: string) => void;
+ /**
+ * `name` attribute for input fields
+ */
+ name?: string;
+ /**
+ * `form` attribute for hidden input
+ */
+ form?: string;
+ /**
+ * If set, the input's value will be masked just like password input. This field is `false` by default
+ */
+ mask?: boolean;
+ /**
+ * If set, the pin input component signals to its fields that they should
+ * use `autocomplete="one-time-code"`. This field is `false` by default
+ */
+ otp?: boolean;
+ /**
+ * Uncontrolled pin input default value.
+ */
+ defaultValue?: string;
+ /**
+ * Controlled pin input value.
+ */
+ value?: string;
+ /**
+ * The type of value pin input should allow, `alphanumeric` by default
+ */
+ type?: "numeric" | "alphanumeric";
+ /**
+ * Placeholder for input fields, `â—‹` by default
+ */
+ placeholder?: string;
+ /**
+ * If set, the user cannot set the value, `false` by default
+ */
+ readOnly?: boolean;
+ /**
+ * If set, the input fields are disabled, `false` by default
+ */
+ disabled?: boolean;
+ ref?: React.Ref;
}
-const PinInputContext = React.createContext(false)
+const PinInputContext = React.createContext(false);
const PinInput = ({ className, children, ref, ...props }: PinInputProps) => {
- const {
- defaultValue,
- value,
- onChange,
- onComplete,
- onIncomplete,
- placeholder = 'â—‹',
- type = 'alphanumeric',
- name,
- form,
- otp = false,
- mask = false,
- disabled = false,
- readOnly = false,
- autoFocus = false,
- ariaLabel = '',
- ...rest
- } = props
+ const {
+ defaultValue,
+ value,
+ onChange,
+ onComplete,
+ onIncomplete,
+ placeholder = "â—‹",
+ type = "alphanumeric",
+ name,
+ form,
+ otp = false,
+ mask = false,
+ disabled = false,
+ readOnly = false,
+ autoFocus = false,
+ ariaLabel = "",
+ ...rest
+ } = props;
- const validChildren = getValidChildren(children)
+ const validChildren = getValidChildren(children);
- const length = getInputFieldCount(children)
+ const length = getInputFieldCount(children);
- // pins, pinValue, refMap, ...handlers
- const { pins, pinValue, refMap, ...handlers } = usePinInput({
- value,
- defaultValue,
- placeholder,
- type,
- length,
- readOnly,
- })
+ // pins, pinValue, refMap, ...handlers
+ const { pins, pinValue, refMap, ...handlers } = usePinInput({
+ value,
+ defaultValue,
+ placeholder,
+ type,
+ length,
+ readOnly
+ });
- /* call onChange func if pinValue changes */
- React.useEffect(() => {
- if (!onChange) return
- onChange(pinValue)
- }, [onChange, pinValue])
+ /* call onChange func if pinValue changes */
+ React.useEffect(() => {
+ if (!onChange) return;
+ onChange(pinValue);
+ }, [onChange, pinValue]);
- /* call onComplete/onIncomplete func if pinValue is valid and completed/incompleted */
- const completeRef = React.useRef(pinValue.length === length)
- React.useEffect(() => {
- if (pinValue.length === length && completeRef.current === false) {
- completeRef.current = true
- if (onComplete) onComplete(pinValue)
- }
- if (pinValue.length !== length && completeRef.current === true) {
- completeRef.current = false
- if (onIncomplete) onIncomplete(pinValue)
- }
- }, [length, onComplete, onIncomplete, pinValue, pins, value])
+ /* call onComplete/onIncomplete func if pinValue is valid and completed/incompleted */
+ const completeRef = React.useRef(pinValue.length === length);
+ React.useEffect(() => {
+ if (pinValue.length === length && completeRef.current === false) {
+ completeRef.current = true;
+ if (onComplete) onComplete(pinValue);
+ }
+ if (pinValue.length !== length && completeRef.current === true) {
+ completeRef.current = false;
+ if (onIncomplete) onIncomplete(pinValue);
+ }
+ }, [length, onComplete, onIncomplete, pinValue, pins, value]);
- /* focus on first input field if autoFocus is set */
- React.useEffect(() => {
- if (!autoFocus) return
- const node = refMap?.get(0)
- if (node) {
- node.focus()
- }
- }, [autoFocus, refMap])
+ /* focus on first input field if autoFocus is set */
+ React.useEffect(() => {
+ if (!autoFocus) return;
+ const node = refMap?.get(0);
+ if (node) {
+ node.focus();
+ }
+ }, [autoFocus, refMap]);
- const skipRef = React.useRef(0)
- let counter = 0
- const clones = validChildren.map((child) => {
- if (child.type === PinInputField) {
- const pinIndex = counter
- counter = counter + 1
- return React.cloneElement(child, {
- name,
- inputKey: `input-${pinIndex}`,
- value: length > pinIndex ? pins[pinIndex] : '',
- onChange: (e: React.ChangeEvent) =>
- handlers.handleChange(e, pinIndex),
- onFocus: (e: React.FocusEvent) =>
- handlers.handleFocus(e, pinIndex),
- onBlur: () => handlers.handleBlur(pinIndex),
- onKeyDown: (e: React.KeyboardEvent) =>
- handlers.handleKeyDown(e, pinIndex),
- onPaste: (e: React.ClipboardEvent) =>
- handlers.handlePaste(e),
- placeholder: placeholder,
- type: type,
- mask: mask,
- autoComplete: otp ? 'one-time-code' : 'off',
- disabled: disabled,
- readOnly: readOnly,
- 'aria-label': ariaLabel
- ? ariaLabel
- : `Pin input ${counter} of ${length}`,
- ref: (node: HTMLInputElement | null) => {
- if (node) {
- refMap?.set(pinIndex, node)
- } else {
- refMap?.delete(pinIndex)
- }
- },
- })
- }
- skipRef.current = skipRef.current + 1
- return child
- })
+ const skipRef = React.useRef(0);
+ let counter = 0;
+ const clones = validChildren.map((child) => {
+ if (child.type === PinInputField) {
+ const pinIndex = counter;
+ counter = counter + 1;
+ return React.cloneElement(child, {
+ name,
+ inputKey: `input-${pinIndex}`,
+ value: length > pinIndex ? pins[pinIndex] : "",
+ onChange: (e: React.ChangeEvent) =>
+ handlers.handleChange(e, pinIndex),
+ onFocus: (e: React.FocusEvent) =>
+ handlers.handleFocus(e, pinIndex),
+ onBlur: () => handlers.handleBlur(pinIndex),
+ onKeyDown: (e: React.KeyboardEvent) =>
+ handlers.handleKeyDown(e, pinIndex),
+ onPaste: (e: React.ClipboardEvent) =>
+ handlers.handlePaste(e),
+ placeholder: placeholder,
+ type: type,
+ mask: mask,
+ autoComplete: otp ? "one-time-code" : "off",
+ disabled: disabled,
+ readOnly: readOnly,
+ "aria-label": ariaLabel
+ ? ariaLabel
+ : `Pin input ${counter} of ${length}`,
+ ref: (node: HTMLInputElement | null) => {
+ if (node) {
+ refMap?.set(pinIndex, node);
+ } else {
+ refMap?.delete(pinIndex);
+ }
+ }
+ });
+ }
+ skipRef.current = skipRef.current + 1;
+ return child;
+ });
- return (
-
-
- {clones}
-
-
-
- )
-}
-PinInput.displayName = 'PinInput'
+ return (
+
+
+ {clones}
+
+
+
+ );
+};
+PinInput.displayName = "PinInput";
/* ========== PinInputField ========== */
interface _PinInputFieldProps {
- mask: boolean
- inputKey: string
- type: 'numeric' | 'alphanumeric'
+ mask: boolean;
+ inputKey: string;
+ type: "numeric" | "alphanumeric";
}
interface PinInputFieldProps
- extends Omit<
- React.ComponentPropsWithoutRef<'input'>,
- keyof _PinInputFieldProps
- > {
- component?: T
+ extends Omit<
+ React.ComponentPropsWithoutRef<"input">,
+ keyof _PinInputFieldProps
+ > {
+ component?: T;
}
-const PinInputField = ({
- className,
- component,
- ...props
+const PinInputField = ({
+ className,
+ component,
+ ...props
}: PinInputFieldProps &
- (React.ComponentType extends undefined
- ? never
- : React.ComponentProps)) => {
- const { mask, type, inputKey, ...rest } = props as _PinInputFieldProps &
- React.ComponentProps
+ (React.ComponentType extends undefined
+ ? never
+ : React.ComponentProps)) => {
+ const { mask, type, inputKey, ...rest } = props as _PinInputFieldProps &
+ React.ComponentProps;
- // Check if PinInputField is used within PinInput
- const isInsidePinInput = React.useContext(PinInputContext)
- if (!isInsidePinInput) {
- throw new Error(
- `PinInputField must be used within ${PinInput.displayName}.`
- )
- }
+ // Check if PinInputField is used within PinInput
+ const isInsidePinInput = React.useContext(PinInputContext);
+ if (!isInsidePinInput) {
+ throw new Error(
+ `PinInputField must be used within ${PinInput.displayName}.`
+ );
+ }
- const Element = component || 'input'
+ const Element = component || "input";
- return (
-
- )
-}
+ return (
+
+ );
+};
/* ========== usePinInput custom hook ========== */
interface UsePinInputProps {
- value: string | undefined
- defaultValue: string | undefined
- placeholder: string
- type: 'numeric' | 'alphanumeric'
- length: number
- readOnly: boolean
+ value: string | undefined;
+ defaultValue: string | undefined;
+ placeholder: string;
+ type: "numeric" | "alphanumeric";
+ length: number;
+ readOnly: boolean;
}
const usePinInput = ({
- value,
- defaultValue,
- placeholder,
- type,
- length,
- readOnly,
+ value,
+ defaultValue,
+ placeholder,
+ type,
+ length,
+ readOnly
}: UsePinInputProps) => {
- const pinInputs = React.useMemo(
- () =>
- Array.from({ length }, (_, index) =>
- defaultValue
- ? defaultValue.charAt(index)
- : value
- ? value.charAt(index)
- : ''
- ),
- [defaultValue, length, value]
- )
+ const pinInputs = React.useMemo(
+ () =>
+ Array.from({ length }, (_, index) =>
+ defaultValue
+ ? defaultValue.charAt(index)
+ : value
+ ? value.charAt(index)
+ : ""
+ ),
+ [defaultValue, length, value]
+ );
- const [pins, setPins] = React.useState(pinInputs)
- const pinValue = pins.join('').trim()
+ const [pins, setPins] = React.useState(pinInputs);
+ const pinValue = pins.join("").trim();
- /**
- * Update pins when values change programmatically.
- * This syncs the pins if the `defaultValue` or `value` prop is updated.
- */
- React.useEffect(() => {
- setPins(pinInputs)
- }, [pinInputs])
+ /**
+ * Update pins when values change programmatically.
+ * This syncs the pins if the `defaultValue` or `value` prop is updated.
+ */
+ React.useEffect(() => {
+ setPins(pinInputs);
+ }, [pinInputs]);
- const itemsRef = React.useRef