-
+
-
- {session?.user?.name}
-
+
+
+ {session?.user?.name}
+
+
+
{session?.user?.email}
@@ -113,21 +116,22 @@ const NavUser = () => {
-
-
- {t('nav.account')}
-
-
-
- {t('nav.change_password')}
-
+
+
+
+ {t('nav.account')}
+
+
+
+
+
+ {t('nav.change_password')}
+
+
-
+
{t('ui.logout_btn')}
diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx
index d2f212b..f9fc6f5 100644
--- a/src/components/ui/badge.tsx
+++ b/src/components/ui/badge.tsx
@@ -1,36 +1,44 @@
-import * as React from "react"
-import { cva, type VariantProps } from "class-variance-authority"
-import { Slot } from "radix-ui"
+import { cva, type VariantProps } from 'class-variance-authority';
+import { Slot } from 'radix-ui';
+import * as React from 'react';
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils';
const badgeVariants = cva(
- "h-5 gap-1 rounded-full border border-transparent px-2 py-0.5 text-[0.625rem] font-medium transition-all has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-2.5! inline-flex items-center justify-center w-fit whitespace-nowrap shrink-0 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-colors overflow-hidden group/badge",
+ 'h-5 gap-1 rounded-full border border-transparent px-2 py-0.5 text-[0.625rem] font-medium transition-all has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-2.5! inline-flex items-center justify-center w-fit whitespace-nowrap shrink-0 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-colors overflow-hidden group/badge',
{
variants: {
variant: {
- default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
- secondary: "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
- destructive: "bg-destructive/10 [a]:hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 text-destructive dark:bg-destructive/20",
- outline: "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground bg-input/20 dark:bg-input/30",
- ghost: "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
- link: "text-primary underline-offset-4 hover:underline",
+ default: 'bg-primary text-primary-foreground [a]:hover:bg-primary/80',
+ secondary:
+ 'bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80',
+ destructive:
+ 'bg-destructive/10 [a]:hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 text-destructive dark:bg-destructive/20',
+ outline:
+ 'border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground bg-input/20 dark:bg-input/30',
+ ghost:
+ 'hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50',
+ link: 'text-primary underline-offset-4 hover:underline',
+ admin: 'bg-cyan-100 text-cyan-600 [a]:hover:bg-cyan-200 ',
+ user: 'bg-green-100 text-green-600 [a]:hover:bg-green-200',
+ member: 'bg-blue-100 text-blue-600 [a]:hover:bg-blue-200',
+ owner: 'bg-red-100 text-red-600 [a]:hover:bg-red-200',
},
},
defaultVariants: {
- variant: "default",
+ variant: 'default',
},
- }
-)
+ },
+);
function Badge({
className,
- variant = "default",
+ variant = 'default',
asChild = false,
...props
-}: React.ComponentProps<"span"> &
+}: React.ComponentProps<'span'> &
VariantProps & { asChild?: boolean }) {
- const Comp = asChild ? Slot.Root : "span"
+ const Comp = asChild ? Slot.Root : 'span';
return (
- )
+ );
}
-export { Badge, badgeVariants }
+export { Badge, badgeVariants };
diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx
index 35dc1b8..7e9a929 100644
--- a/src/components/ui/dropdown-menu.tsx
+++ b/src/components/ui/dropdown-menu.tsx
@@ -1,13 +1,13 @@
-import * as React from "react"
-import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui"
+import { DropdownMenu as DropdownMenuPrimitive } from 'radix-ui';
+import * as React from 'react';
-import { cn } from "@/lib/utils"
-import { CheckIcon, CaretRightIcon } from "@phosphor-icons/react"
+import { cn } from '@/lib/utils';
+import { CaretRightIcon, CheckIcon } from '@phosphor-icons/react';
function DropdownMenu({
...props
}: React.ComponentProps) {
- return
+ return ;
}
function DropdownMenuPortal({
@@ -15,7 +15,7 @@ function DropdownMenuPortal({
}: React.ComponentProps) {
return (
- )
+ );
}
function DropdownMenuTrigger({
@@ -26,12 +26,12 @@ function DropdownMenuTrigger({
data-slot="dropdown-menu-trigger"
{...props}
/>
- )
+ );
}
function DropdownMenuContent({
className,
- align = "start",
+ align = 'start',
sideOffset = 4,
...props
}: React.ComponentProps) {
@@ -41,11 +41,14 @@ function DropdownMenuContent({
data-slot="dropdown-menu-content"
sideOffset={sideOffset}
align={align}
- className={cn("data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground min-w-32 rounded-lg p-1 shadow-md ring-1 duration-100 z-50 max-h-(--radix-dropdown-menu-content-available-height) w-(--radix-dropdown-menu-trigger-width) origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto data-[state=closed]:overflow-hidden", className )}
+ className={cn(
+ 'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground min-w-32 rounded-lg p-1 shadow-md ring-1 duration-100 z-50 max-h-(--radix-dropdown-menu-content-available-height) w-(--radix-dropdown-menu-trigger-width) origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto data-[state=closed]:overflow-hidden',
+ className,
+ )}
{...props}
/>
- )
+ );
}
function DropdownMenuGroup({
@@ -53,17 +56,17 @@ function DropdownMenuGroup({
}: React.ComponentProps) {
return (
- )
+ );
}
function DropdownMenuItem({
className,
inset,
- variant = "default",
+ variant = 'default',
...props
}: React.ComponentProps & {
- inset?: boolean
- variant?: "default" | "destructive"
+ inset?: boolean;
+ variant?: 'default' | 'destructive';
}) {
return (
- )
+ );
}
function DropdownMenuCheckboxItem({
@@ -90,23 +93,22 @@ function DropdownMenuCheckboxItem({
data-slot="dropdown-menu-checkbox-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground min-h-7 gap-2 rounded-md py-1.5 pr-8 pl-2 text-xs [&_svg:not([class*='size-'])]:size-3.5 relative flex cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
- className
+ className,
)}
checked={checked}
{...props}
>
-
+
{children}
- )
+ );
}
function DropdownMenuRadioGroup({
@@ -117,7 +119,7 @@ function DropdownMenuRadioGroup({
data-slot="dropdown-menu-radio-group"
{...props}
/>
- )
+ );
}
function DropdownMenuRadioItem({
@@ -130,22 +132,21 @@ function DropdownMenuRadioItem({
data-slot="dropdown-menu-radio-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground min-h-7 gap-2 rounded-md py-1.5 pr-8 pl-2 text-xs [&_svg:not([class*='size-'])]:size-3.5 relative flex cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
- className
+ className,
)}
{...props}
>
-
+
{children}
- )
+ );
}
function DropdownMenuLabel({
@@ -153,16 +154,19 @@ function DropdownMenuLabel({
inset,
...props
}: React.ComponentProps & {
- inset?: boolean
+ inset?: boolean;
}) {
return (
- )
+ );
}
function DropdownMenuSeparator({
@@ -172,29 +176,32 @@ function DropdownMenuSeparator({
return (
- )
+ );
}
function DropdownMenuShortcut({
className,
...props
-}: React.ComponentProps<"span">) {
+}: React.ComponentProps<'span'>) {
return (
- )
+ );
}
function DropdownMenuSub({
...props
}: React.ComponentProps) {
- return
+ return ;
}
function DropdownMenuSubTrigger({
@@ -203,7 +210,7 @@ function DropdownMenuSubTrigger({
children,
...props
}: React.ComponentProps & {
- inset?: boolean
+ inset?: boolean;
}) {
return (
{children}
- )
+ );
}
function DropdownMenuSubContent({
@@ -228,26 +235,29 @@ function DropdownMenuSubContent({
return (
- )
+ );
}
export {
DropdownMenu,
- DropdownMenuPortal,
- DropdownMenuTrigger,
+ DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuGroup,
- DropdownMenuLabel,
DropdownMenuItem,
- DropdownMenuCheckboxItem,
+ DropdownMenuLabel,
+ DropdownMenuPortal,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
- DropdownMenuSubTrigger,
DropdownMenuSubContent,
-}
+ DropdownMenuSubTrigger,
+ DropdownMenuTrigger,
+};
diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx
new file mode 100644
index 0000000..cba1d26
--- /dev/null
+++ b/src/components/ui/select.tsx
@@ -0,0 +1,183 @@
+import * as React from "react"
+import { Select as SelectPrimitive } from "radix-ui"
+
+import { cn } from "@/lib/utils"
+import { CaretDownIcon, CheckIcon, CaretUpIcon } from "@phosphor-icons/react"
+
+function Select({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function SelectGroup({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SelectValue({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function SelectTrigger({
+ className,
+ size = "default",
+ children,
+ ...props
+}: React.ComponentProps & {
+ size?: "sm" | "default"
+}) {
+ return (
+
+ {children}
+
+
+
+
+ )
+}
+
+function SelectContent({
+ className,
+ children,
+ position = "item-aligned",
+ align = "center",
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+ {children}
+
+
+
+
+ )
+}
+
+function SelectLabel({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SelectItem({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ )
+}
+
+function SelectSeparator({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SelectScrollUpButton({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+function SelectScrollDownButton({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+export {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectLabel,
+ SelectScrollDownButton,
+ SelectScrollUpButton,
+ SelectSeparator,
+ SelectTrigger,
+ SelectValue,
+}
diff --git a/src/components/ui/sidebar.tsx b/src/components/ui/sidebar.tsx
index dfe6acd..c216672 100644
--- a/src/components/ui/sidebar.tsx
+++ b/src/components/ui/sidebar.tsx
@@ -1,55 +1,55 @@
"use client"
-import * as React from "react"
-import { cva, type VariantProps } from "class-variance-authority"
-import { Slot } from "radix-ui"
+import { cva, type VariantProps } from 'class-variance-authority';
+import { Slot } from 'radix-ui';
+import * as React from 'react';
-import { cn } from "@/lib/utils"
-import { Button } from "@/components/ui/button"
-import { Input } from "@/components/ui/input"
-import { Separator } from "@/components/ui/separator"
+import { Button } from '@/components/ui/button';
+import { Input } from '@/components/ui/input';
+import { Separator } from '@/components/ui/separator';
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
-} from "@/components/ui/sheet"
-import { Skeleton } from "@/components/ui/skeleton"
+} from '@/components/ui/sheet';
+import { Skeleton } from '@/components/ui/skeleton';
import {
Tooltip,
TooltipContent,
TooltipTrigger,
-} from "@/components/ui/tooltip"
-import { useIsMobile } from "@/hooks/use-mobile"
-import { SidebarIcon } from "@phosphor-icons/react"
+} from '@/components/ui/tooltip';
+import { useIsMobile } from '@/hooks/use-mobile';
+import { cn } from '@/lib/utils';
+import { SidebarIcon } from '@phosphor-icons/react';
-const SIDEBAR_COOKIE_NAME = "sidebar_state"
-const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
-const SIDEBAR_WIDTH = "16rem"
-const SIDEBAR_WIDTH_MOBILE = "18rem"
-const SIDEBAR_WIDTH_ICON = "3rem"
-const SIDEBAR_KEYBOARD_SHORTCUT = "b"
+const SIDEBAR_COOKIE_NAME = 'sidebar_state';
+const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
+const SIDEBAR_WIDTH = '16rem';
+const SIDEBAR_WIDTH_MOBILE = '18rem';
+const SIDEBAR_WIDTH_ICON = '3rem';
+const SIDEBAR_KEYBOARD_SHORTCUT = 'b';
type SidebarContextProps = {
- state: "expanded" | "collapsed"
- open: boolean
- setOpen: (open: boolean) => void
- openMobile: boolean
- setOpenMobile: (open: boolean) => void
- isMobile: boolean
- toggleSidebar: () => void
-}
+ state: 'expanded' | 'collapsed';
+ open: boolean;
+ setOpen: (open: boolean) => void;
+ openMobile: boolean;
+ setOpenMobile: (open: boolean) => void;
+ isMobile: boolean;
+ toggleSidebar: () => void;
+};
-const SidebarContext = React.createContext(null)
+const SidebarContext = React.createContext(null);
function useSidebar() {
- const context = React.useContext(SidebarContext)
+ const context = React.useContext(SidebarContext);
if (!context) {
- throw new Error("useSidebar must be used within a SidebarProvider.")
+ throw new Error('useSidebar must be used within a SidebarProvider.');
}
- return context
+ return context;
}
function SidebarProvider({
@@ -60,37 +60,37 @@ function SidebarProvider({
style,
children,
...props
-}: React.ComponentProps<"div"> & {
- defaultOpen?: boolean
- open?: boolean
- onOpenChange?: (open: boolean) => void
+}: React.ComponentProps<'div'> & {
+ defaultOpen?: boolean;
+ open?: boolean;
+ onOpenChange?: (open: boolean) => void;
}) {
- const isMobile = useIsMobile()
- const [openMobile, setOpenMobile] = React.useState(false)
+ const isMobile = useIsMobile();
+ const [openMobile, setOpenMobile] = React.useState(false);
// This is the internal state of the sidebar.
// We use openProp and setOpenProp for control from outside the component.
- const [_open, _setOpen] = React.useState(defaultOpen)
- const open = openProp ?? _open
+ const [_open, _setOpen] = React.useState(defaultOpen);
+ const open = openProp ?? _open;
const setOpen = React.useCallback(
(value: boolean | ((value: boolean) => boolean)) => {
- const openState = typeof value === "function" ? value(open) : value
+ const openState = typeof value === 'function' ? value(open) : value;
if (setOpenProp) {
- setOpenProp(openState)
+ setOpenProp(openState);
} else {
- _setOpen(openState)
+ _setOpen(openState);
}
// This sets the cookie to keep the sidebar state.
- document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
+ document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
},
- [setOpenProp, open]
- )
+ [setOpenProp, open],
+ );
// Helper to toggle the sidebar.
const toggleSidebar = React.useCallback(() => {
- return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open)
- }, [isMobile, setOpen, setOpenMobile])
+ return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);
+ }, [isMobile, setOpen, setOpenMobile]);
// Adds a keyboard shortcut to toggle the sidebar.
React.useEffect(() => {
@@ -99,18 +99,18 @@ function SidebarProvider({
event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
(event.metaKey || event.ctrlKey)
) {
- event.preventDefault()
- toggleSidebar()
+ event.preventDefault();
+ toggleSidebar();
}
- }
+ };
- window.addEventListener("keydown", handleKeyDown)
- return () => window.removeEventListener("keydown", handleKeyDown)
- }, [toggleSidebar])
+ window.addEventListener('keydown', handleKeyDown);
+ return () => window.removeEventListener('keydown', handleKeyDown);
+ }, [toggleSidebar]);
// We add a state so that we can do data-state="expanded" or "collapsed".
// This makes it easier to style the sidebar with Tailwind classes.
- const state = open ? "expanded" : "collapsed"
+ const state = open ? 'expanded' : 'collapsed';
const contextValue = React.useMemo(
() => ({
@@ -122,8 +122,8 @@ function SidebarProvider({
setOpenMobile,
toggleSidebar,
}),
- [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
- )
+ [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar],
+ );
return (
@@ -131,50 +131,50 @@ function SidebarProvider({
data-slot="sidebar-wrapper"
style={
{
- "--sidebar-width": SIDEBAR_WIDTH,
- "--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
+ '--sidebar-width': SIDEBAR_WIDTH,
+ '--sidebar-width-icon': SIDEBAR_WIDTH_ICON,
...style,
} as React.CSSProperties
}
className={cn(
- "group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full",
- className
+ 'group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full',
+ className,
)}
{...props}
>
{children}
- )
+ );
}
function Sidebar({
- side = "left",
- variant = "sidebar",
- collapsible = "offExamples",
+ side = 'left',
+ variant = 'sidebar',
+ collapsible = 'offExamples',
className,
children,
...props
-}: React.ComponentProps<"div"> & {
- side?: "left" | "right"
- variant?: "sidebar" | "floating" | "inset"
- collapsible?: "offExamples" | "icon" | "none"
+}: React.ComponentProps<'div'> & {
+ side?: 'left' | 'right';
+ variant?: 'sidebar' | 'floating' | 'inset';
+ collapsible?: 'offExamples' | 'icon' | 'none';
}) {
- const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
+ const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
- if (collapsible === "none") {
+ if (collapsible === 'none') {
return (
{children}
- )
+ );
}
if (isMobile) {
@@ -187,7 +187,7 @@ function Sidebar({
className="bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden"
style={
{
- "--sidebar-width": SIDEBAR_WIDTH_MOBILE,
+ '--sidebar-width': SIDEBAR_WIDTH_MOBILE,
} as React.CSSProperties
}
side={side}
@@ -199,14 +199,14 @@ function Sidebar({
{children}
- )
+ );
}
return (
@@ -247,7 +247,7 @@ function Sidebar({