Преглед изворни кода

refactor!: reorganize project to feature-based structure

BREAKING CHANGE: Restructured the entire folder
hierarchy to adopt a feature-based structure. This
change improves code modularity and maintainability
but introduces breaking changes.
satnaing пре 1 година
родитељ
комит
546842f14f
100 измењених фајлова са 2147 додато и 1547 уклоњено
  1. 0 20
      src/components/app-shell.tsx
  2. 0 0
      src/components/button.tsx
  3. 0 16
      src/components/coming-soon.tsx
  4. 0 85
      src/components/custom/breadcrumb.tsx
  5. 0 373
      src/components/custom/pin-input-original.tsx
  6. 0 61
      src/components/language-provider.tsx
  7. 0 49
      src/components/language-switch.tsx
  8. 221 0
      src/components/layout/app-sidebar.tsx
  9. 12 2
      src/components/layout/index.tsx
  10. 109 0
      src/components/layout/nav-group.tsx
  11. 107 0
      src/components/layout/nav-user.tsx
  12. 86 0
      src/components/layout/team-switcher.tsx
  13. 10 9
      src/components/layout/top-nav.tsx
  14. 42 0
      src/components/layout/types.ts
  15. 0 10
      src/components/loader.tsx
  16. 0 243
      src/components/nav.tsx
  17. 5 3
      src/components/password-input.tsx
  18. 0 0
      src/components/pin-input.tsx
  19. 7 9
      src/components/profile-dropdown.tsx
  20. 1 3
      src/components/search.tsx
  21. 0 126
      src/components/sidebar.tsx
  22. 4 6
      src/components/theme-switch.tsx
  23. 0 1
      src/components/ui/avatar.tsx
  24. 0 1
      src/components/ui/badge.tsx
  25. 56 0
      src/components/ui/button.tsx
  26. 5 6
      src/components/ui/calendar.tsx
  27. 6 7
      src/components/ui/card.tsx
  28. 2 3
      src/components/ui/checkbox.tsx
  29. 4 7
      src/components/ui/command.tsx
  30. 4 3
      src/components/ui/dialog.tsx
  31. 6 11
      src/components/ui/dropdown-menu.tsx
  32. 2 3
      src/components/ui/form.tsx
  33. 2 6
      src/components/ui/input.tsx
  34. 0 1
      src/components/ui/label.tsx
  35. 0 1
      src/components/ui/popover.tsx
  36. 2 3
      src/components/ui/radio-group.tsx
  37. 5 11
      src/components/ui/select.tsx
  38. 0 1
      src/components/ui/separator.tsx
  39. 139 0
      src/components/ui/sheet.tsx
  40. 768 0
      src/components/ui/sidebar.tsx
  41. 15 0
      src/components/ui/skeleton.tsx
  42. 0 1
      src/components/ui/switch.tsx
  43. 0 1
      src/components/ui/table.tsx
  44. 0 1
      src/components/ui/tabs.tsx
  45. 15 18
      src/components/ui/textarea.tsx
  46. 2 3
      src/components/ui/toast.tsx
  47. 1 1
      src/components/ui/toaster.tsx
  48. 11 10
      src/components/ui/tooltip.tsx
  49. 0 0
      src/context/theme-context.tsx
  50. 0 176
      src/data/sidelinks.tsx
  51. 0 0
      src/features/apps/data/apps.tsx
  52. 6 6
      src/features/apps/index.tsx
  53. 28 0
      src/features/auth/auth-layout.tsx
  54. 6 5
      src/features/auth/forgot-password/components/forgot-password-form.tsx
  55. 33 0
      src/features/auth/forgot-password/index.tsx
  56. 7 6
      src/features/auth/otp/components/otp-form.tsx
  57. 33 0
      src/features/auth/otp/index.tsx
  58. 7 6
      src/features/auth/sign-in/components/user-auth-form.tsx
  59. 37 0
      src/features/auth/sign-in/index.tsx
  60. 75 0
      src/features/auth/sign-in/sign-in-2.tsx
  61. 6 5
      src/features/auth/sign-up/components/sign-up-form.tsx
  62. 46 0
      src/features/auth/sign-up/index.tsx
  63. 0 0
      src/features/chats/data/convo.json
  64. 10 11
      src/features/chats/index.tsx
  65. 0 0
      src/features/dashboard/components/overview.tsx
  66. 0 0
      src/features/dashboard/components/recent-sales.tsx
  67. 38 38
      src/features/dashboard/index.tsx
  68. 25 0
      src/features/errors/forbidden.tsx
  69. 8 9
      src/features/errors/general-error.tsx
  70. 5 6
      src/features/errors/maintenance-error.tsx
  71. 25 0
      src/features/errors/not-found-error.tsx
  72. 25 0
      src/features/errors/unauthorized-error.tsx
  73. 7 7
      src/features/settings/account/account-form.tsx
  74. 1 1
      src/features/settings/account/index.tsx
  75. 5 6
      src/features/settings/appearance/appearance-form.tsx
  76. 1 1
      src/features/settings/appearance/index.tsx
  77. 0 0
      src/features/settings/components/content-section.tsx
  78. 5 4
      src/features/settings/components/sidebar-nav.tsx
  79. 4 5
      src/features/settings/display/display-form.tsx
  80. 1 1
      src/features/settings/display/index.tsx
  81. 6 12
      src/features/settings/index.tsx
  82. 1 1
      src/features/settings/notifications/index.tsx
  83. 8 9
      src/features/settings/notifications/notifications-form.tsx
  84. 1 1
      src/features/settings/profile/index.tsx
  85. 6 6
      src/features/settings/profile/profile-form.tsx
  86. 2 4
      src/features/tasks/components/columns.tsx
  87. 2 3
      src/features/tasks/components/data-table-column-header.tsx
  88. 1 2
      src/features/tasks/components/data-table-faceted-filter.tsx
  89. 1 2
      src/features/tasks/components/data-table-pagination.tsx
  90. 1 3
      src/features/tasks/components/data-table-row-actions.tsx
  91. 1 3
      src/features/tasks/components/data-table-toolbar.tsx
  92. 1 2
      src/features/tasks/components/data-table-view-options.tsx
  93. 0 2
      src/features/tasks/components/data-table.tsx
  94. 17 17
      src/features/tasks/data/data.tsx
  95. 0 0
      src/features/tasks/data/schema.ts
  96. 0 0
      src/features/tasks/data/tasks.ts
  97. 6 6
      src/features/tasks/index.tsx
  98. 0 15
      src/hooks/use-check-active-nav.tsx
  99. 0 29
      src/hooks/use-is-collapsed.tsx
  100. 0 22
      src/hooks/use-local-storage.tsx

+ 0 - 20
src/components/app-shell.tsx

@@ -1,20 +0,0 @@
-import { Outlet } from 'react-router-dom'
-import Sidebar from './sidebar'
-import useIsCollapsed from '@/hooks/use-is-collapsed'
-import SkipToMain from './skip-to-main'
-
-export default function AppShell() {
-  const [isCollapsed, setIsCollapsed] = useIsCollapsed()
-  return (
-    <div className='relative h-full overflow-hidden bg-background'>
-      <SkipToMain />
-      <Sidebar isCollapsed={isCollapsed} setIsCollapsed={setIsCollapsed} />
-      <main
-        id='content'
-        className={`overflow-x-hidden pt-16 transition-[margin] md:overflow-y-hidden md:pt-0 ${isCollapsed ? 'md:ml-14' : 'md:ml-64'} h-full`}
-      >
-        <Outlet />
-      </main>
-    </div>
-  )
-}

+ 0 - 0
src/components/custom/button.tsx → src/components/button.tsx


+ 0 - 16
src/components/coming-soon.tsx

@@ -1,16 +0,0 @@
-import { IconPlanet } from '@tabler/icons-react'
-
-export default function ComingSoon() {
-  return (
-    <div className='h-svh'>
-      <div className='m-auto flex h-full w-full flex-col items-center justify-center gap-2'>
-        <IconPlanet size={72} />
-        <h1 className='text-4xl font-bold leading-tight'>Coming Soon 👀</h1>
-        <p className='text-center text-muted-foreground'>
-          This page has not been created yet. <br />
-          Stay tuned though!
-        </p>
-      </div>
-    </div>
-  )
-}

+ 0 - 85
src/components/custom/breadcrumb.tsx

@@ -1,85 +0,0 @@
-import * as React from 'react'
-import { cn } from '@/lib/utils'
-
-interface BreadcrumbProps extends React.ComponentPropsWithoutRef<'nav'> {
-  children:
-    | React.ReactElement<typeof BreadcrumbItem>
-    | React.ReactElement<typeof BreadcrumbItem>[]
-  separator?: React.ReactNode
-}
-
-const BreadcrumbContext = React.createContext<boolean>(false)
-
-const Breadcrumb = React.forwardRef<HTMLElement, BreadcrumbProps>(
-  ({ className, children, separator, ...props }, ref) => {
-    const validChildren = getValidChildren(children)
-
-    const count = validChildren.length
-
-    const clones = validChildren.map((child, index) =>
-      React.cloneElement(child, {
-        separator,
-        isLastChild: count === index + 1,
-      })
-    )
-
-    return (
-      <BreadcrumbContext.Provider value={true}>
-        <nav ref={ref} aria-label='breadcrumb' className={className} {...props}>
-          <ol className={cn(`flex`)}>{clones}</ol>
-        </nav>
-      </BreadcrumbContext.Provider>
-    )
-  }
-)
-Breadcrumb.displayName = 'Breadcrumb'
-
-interface InternalBreadcrumbItemProps {
-  separator?: React.ReactNode
-  isLastChild: boolean
-}
-
-interface BreadcrumbItemProps
-  extends Omit<
-    React.ComponentPropsWithoutRef<'li'>,
-    keyof InternalBreadcrumbItemProps
-  > {}
-
-const BreadcrumbItem = React.forwardRef<HTMLLIElement, BreadcrumbItemProps>(
-  ({ className, children, ...props }, ref) => {
-    const { separator, isLastChild, ...rest } =
-      props as InternalBreadcrumbItemProps
-
-    // Check if BreadcrumbItem is used within Breadcrumb
-    const isInsideBreadcrumb = React.useContext(BreadcrumbContext)
-    if (!isInsideBreadcrumb) {
-      throw new Error(
-        `${BreadcrumbItem.displayName} must be used within ${Breadcrumb.displayName}.`
-      )
-    }
-
-    return (
-      <li ref={ref} className={cn(`group`, className)} {...rest}>
-        {children}
-        {!isLastChild && (
-          <span className='mx-2 *:!inline-block'>{separator ?? '/'}</span>
-        )}
-      </li>
-    )
-  }
-)
-BreadcrumbItem.displayName = 'BreadcrumbItem'
-
-/* ========== Util Func ========== */
-
-const getValidChildren = (children: React.ReactNode) =>
-  React.Children.toArray(children).filter((child) => {
-    if (React.isValidElement(child) && child.type === BreadcrumbItem) {
-      return React.isValidElement(child)
-    }
-    throw new Error(
-      `${Breadcrumb.displayName} can only have ${BreadcrumbItem.displayName} as children.`
-    )
-  }) as React.ReactElement[]
-
-export { Breadcrumb, BreadcrumbItem }

+ 0 - 373
src/components/custom/pin-input-original.tsx

@@ -1,373 +0,0 @@
-import * as React from 'react'
-import { Input } from '@/components/ui/input'
-import { cn } from '@/lib/utils'
-
-interface PinInputOgProps {
-  /**
-   * id for input fields.
-   */
-  id?: string
-  /**
-   * Class name for input fields.
-   */
-  className?: string
-  /**
-   * Class name for input fields container, `flex gap-2` by default
-   */
-  containerClassName?: string
-  /**
-   * Uncontrolled pin input default value.
-   */
-  defaultValue?: string
-  /**
-   * Controlled pin input value.
-   */
-  value?: string
-  /**
-   * Called when value changes
-   */
-  onChange?: (value: string) => void
-  /**
-   * Called when all input have valid value
-   */
-  onComplete?: (value: string) => void
-  /**
-   * The type of value pin input should allow, `alphanumeric` by default
-   */
-  type?: 'numeric' | 'alphanumeric'
-  /**
-   * Placeholder for input fields, `○` by default
-   */
-  placeholder?: string
-  /**
-   * Number of pin input fields, `4` by default
-   */
-  length?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 // 4 by default
-  /**
-   * `name` attribute for hidden input
-   */
-  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
-  /**
-   * If set, the input fields are disabled, `false` by default
-   */
-  disabled?: boolean
-  /**
-   * If set, the user cannot set the value, `false` by default
-   */
-  readOnly?: boolean
-  /**
-   * If set, the pin input receives focus on mount, `false` by default
-   */
-  autoFocus?: boolean
-  /**
-   * `aria-label` for the input fields
-   */
-  ariaLabel?: string
-}
-
-const PinInputOg = ({
-  containerClassName,
-  id,
-  className,
-  type = 'alphanumeric',
-  placeholder = '○',
-  length = 4,
-  name,
-  form,
-  defaultValue,
-  value,
-  onChange,
-  onComplete,
-  mask = false,
-  otp = false,
-  disabled = false,
-  readOnly = false,
-  autoFocus = false,
-  ariaLabel,
-}: PinInputOgProps) => {
-  if (length < 1 || length > 12) {
-    throw new Error('input length cannot be more than 12 or less than 1')
-  }
-
-  if ((value !== undefined && !onChange) || (value === undefined && onChange)) {
-    throw new Error(
-      'if one of value or onChange is specified, both props must be set.'
-    )
-  }
-
-  const { pins, pinValue, refMap, ...handlers } = usePinInput({
-    value,
-    defaultValue,
-    placeholder,
-    type,
-    length,
-    readOnly,
-  })
-
-  /* call onChange func if pinValue changes */
-  React.useEffect(() => {
-    onChange && onChange(pinValue)
-  }, [onChange, pinValue])
-
-  /* call onComplete func if pinValue is valid and completed */
-  React.useEffect(() => {
-    if (onComplete && pinValue.length === length) {
-      onComplete(pinValue)
-    }
-  }, [length, onComplete, pinValue])
-
-  /* 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])
-
-  return (
-    <div className={cn('flex gap-2', containerClassName)}>
-      {pins.map((pin, i) => (
-        <PinInputField
-          key={i}
-          id={i === 0 ? id : undefined}
-          defaultValue={pin}
-          onChange={(e) => handlers.handleChange(e, i)}
-          onFocus={(e) => handlers.handleFocus(e, i)}
-          onBlur={() => handlers.handleBlur(i)}
-          onKeyDown={(e) => handlers.handleKeyDown(e, i)}
-          onPaste={handlers.handlePaste}
-          placeholder={placeholder}
-          className={className}
-          type={type}
-          mask={mask}
-          autoComplete={otp ? 'one-time-code' : 'off'}
-          disabled={disabled}
-          readOnly={readOnly}
-          aria-label={ariaLabel}
-          ref={(node) => {
-            if (node) {
-              refMap?.set(i, node)
-            } else {
-              refMap?.delete(i)
-            }
-          }}
-        />
-      ))}
-      <input type='hidden' name={name} form={form} value={pinValue} />
-    </div>
-  )
-}
-PinInputOg.displayName = 'PinInputOg'
-
-interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
-  mask?: boolean
-}
-
-const PinInputField = React.forwardRef<HTMLInputElement, InputProps>(
-  ({ className, type, mask, ...props }, ref) => {
-    const inputType = mask ? 'password' : type === 'numeric' ? 'tel' : 'text'
-    return (
-      <Input
-        ref={ref}
-        type={inputType}
-        inputMode={type === 'numeric' ? 'numeric' : 'text'}
-        className={cn('size-10 text-center', className)}
-        {...props}
-      />
-    )
-  }
-)
-
-/* ========== usePinInput custom hook ========== */
-
-interface UsePinInputProps {
-  value: string | undefined
-  defaultValue: string | undefined
-  placeholder: string
-  type: 'numeric' | 'alphanumeric'
-  length: number
-  readOnly: boolean
-}
-
-const usePinInput = ({
-  value,
-  defaultValue,
-  placeholder,
-  type,
-  length,
-  readOnly,
-}: UsePinInputProps) => {
-  const pinInputs = Array.from({ length }, (_, index) =>
-    defaultValue ? defaultValue.charAt(index) : value ? value.charAt(index) : ''
-  )
-  const [pins, setPins] = React.useState<(string | number)[]>(pinInputs)
-  const pinValue = pins.join('').trim()
-
-  const itemsRef = React.useRef<Map<number, HTMLInputElement> | null>(null)
-
-  function getMap() {
-    if (!itemsRef.current) {
-      // Initialize the Map on first usage.
-      itemsRef.current = new Map()
-    }
-    return itemsRef.current
-  }
-
-  function getNode(index: number) {
-    const map = getMap()
-    const node = map?.get(index)
-    return node
-  }
-
-  function focusInput(itemId: number) {
-    const node = getNode(itemId)
-    if (node) {
-      node.focus()
-      node.placeholder = ''
-    }
-  }
-
-  function handleFocus(
-    event: React.FocusEvent<HTMLInputElement>,
-    index: number
-  ) {
-    event.target.select()
-    focusInput(index)
-  }
-
-  function handleBlur(index: number) {
-    const node = getNode(index)
-    if (node) {
-      node.placeholder = placeholder
-    }
-  }
-
-  function updateInputField(val: string, index: number) {
-    const node = getNode(index)
-
-    if (node) {
-      node.value = val
-    }
-
-    setPins((prev) =>
-      prev.map((p, i) => {
-        if (i === index) {
-          return val
-        } else {
-          return p
-        }
-      })
-    )
-  }
-
-  function validate(value: string) {
-    const NUMERIC_REGEX = /^[0-9]+$/
-    const ALPHA_NUMERIC_REGEX = /^[a-zA-Z0-9]+$/i
-    const regex = type === 'alphanumeric' ? ALPHA_NUMERIC_REGEX : NUMERIC_REGEX
-    return regex.test(value)
-  }
-
-  const pastedVal = React.useRef<null | string>(null)
-  function handleChange(e: React.ChangeEvent<HTMLInputElement>, index: number) {
-    const inputValue = e.target.value
-    const pastedValue = pastedVal.current
-    const inputChar = pastedValue
-      ? pastedValue.charAt(length - 1)
-      : inputValue.slice(-1)
-
-    if (validate(inputChar)) {
-      updateInputField(inputChar, index)
-      pastedVal.current = null
-      if (inputValue.length > 0) {
-        focusInput(index + 1)
-      }
-    }
-  }
-
-  function handlePaste(event: React.ClipboardEvent<HTMLInputElement>) {
-    event.preventDefault()
-    const copyValue = event.clipboardData
-      .getData('text/plain')
-      .replace(/[\n\r\s]+/g, '')
-    const copyArr = copyValue.split('').slice(0, length)
-
-    const isValid = copyArr.every((c) => validate(c))
-
-    if (!isValid) return
-
-    for (let i = 0; i < length; i++) {
-      if (i < copyArr.length) {
-        updateInputField(copyArr[i], i)
-      }
-    }
-
-    pastedVal.current = copyValue
-    focusInput(copyArr.length < length ? copyArr.length : length - 1)
-  }
-
-  function handleKeyDown(
-    event: React.KeyboardEvent<HTMLInputElement>,
-    index: number
-  ) {
-    const { ctrlKey, key, shiftKey, metaKey } = event
-
-    if (type === 'numeric') {
-      const canTypeSign =
-        key === 'Backspace' ||
-        key === 'Tab' ||
-        key === 'Control' ||
-        key === 'Delete' ||
-        (ctrlKey && key === 'v') ||
-        (metaKey && key === 'v')
-          ? true
-          : !Number.isNaN(Number(key))
-
-      if (!canTypeSign || readOnly) {
-        event.preventDefault()
-      }
-    }
-
-    if (key === 'ArrowLeft' || (shiftKey && key === 'Tab')) {
-      event.preventDefault()
-      focusInput(index - 1)
-    } else if (key === 'ArrowRight' || key === 'Tab' || key === ' ') {
-      event.preventDefault()
-      focusInput(index + 1)
-    } else if (key === 'Delete') {
-      event.preventDefault()
-    } else if (key === 'Backspace') {
-      event.preventDefault()
-      updateInputField('', index)
-      if ((event.target as HTMLInputElement).value === '') {
-        focusInput(index - 1)
-      }
-    }
-  }
-
-  return {
-    pins,
-    pinValue,
-    refMap: getMap(),
-    handleFocus,
-    handleBlur,
-    handleChange,
-    handlePaste,
-    handleKeyDown,
-  }
-}
-
-export { PinInputOg }

+ 0 - 61
src/components/language-provider.tsx

@@ -1,61 +0,0 @@
-import { createContext, useContext, useState } from 'react'
-import { IntlProvider } from 'use-intl'
-import translations from '../translations'
-
-export type Language = 'en' | 'zh'
-
-type LanguageProviderProps = {
-  children: React.ReactNode
-  defaultLanguage?: Language
-  storageKey?: string
-}
-
-type LanguageProviderState = {
-  language: Language
-  setLanguage: (lang: Language) => void
-}
-
-const initialState: LanguageProviderState = {
-  language: 'en',
-  setLanguage: () => null,
-}
-
-const LanguageProviderContext =
-  createContext<LanguageProviderState>(initialState)
-
-export function LanguageProvider({
-  children,
-  defaultLanguage = 'en',
-  storageKey = 'vite-ui-language',
-  ...props
-}: LanguageProviderProps) {
-  const [language, setLanguage] = useState<Language>(
-    () => (localStorage.getItem(storageKey) as Language) || defaultLanguage
-  )
-
-  const value = {
-    language,
-    setLanguage: (lang: Language) => {
-      localStorage.setItem(storageKey, lang)
-      setLanguage(lang)
-    },
-  }
-
-  return (
-    <LanguageProviderContext.Provider {...props} value={value}>
-      <IntlProvider locale={language} messages={translations[language]}>
-        {children}
-      </IntlProvider>
-    </LanguageProviderContext.Provider>
-  )
-}
-
-// eslint-disable-next-line react-refresh/only-export-components
-export const useLanguage = () => {
-  const context = useContext(LanguageProviderContext)
-
-  if (context === undefined)
-    throw new Error('useLanguage must be used within a LanguageProvider')
-
-  return context
-}

+ 0 - 49
src/components/language-switch.tsx

@@ -1,49 +0,0 @@
-import { Language, useLanguage } from './language-provider'
-import { IconCheck } from '@tabler/icons-react'
-import { cn } from '@/lib/utils'
-import {
-  DropdownMenu,
-  DropdownMenuContent,
-  DropdownMenuItem,
-  DropdownMenuTrigger,
-} from '@/components/ui/dropdown-menu'
-import { Button } from './custom/button'
-
-export default function LanguageSwitch() {
-  const { language, setLanguage } = useLanguage()
-
-  const languageText = new Map<string, string>([
-    ['en', 'English'],
-    ['zh', '简体中文'],
-  ])
-
-  const renderDropdownItem = () => {
-    return Array.from(languageText).map(([key, value]) => (
-      <DropdownMenuItem key={key} onClick={() => setLanguage(key as Language)}>
-        {value}{' '}
-        <IconCheck
-          size={14}
-          className={cn('ml-auto', language !== key && 'hidden')}
-        />
-      </DropdownMenuItem>
-    ))
-  }
-
-  return (
-    <DropdownMenu>
-      <DropdownMenuTrigger asChild>
-        <Button
-          variant='outline'
-          size='default'
-          className='scale-95 rounded-full'
-        >
-          {languageText.get(language)}
-          <span className='sr-only'>Toggle language</span>
-        </Button>
-      </DropdownMenuTrigger>
-      <DropdownMenuContent align='end'>
-        {renderDropdownItem()}
-      </DropdownMenuContent>
-    </DropdownMenu>
-  )
-}

+ 221 - 0
src/components/layout/app-sidebar.tsx

@@ -0,0 +1,221 @@
+import {
+  IconBarrierBlock,
+  IconBrowserCheck,
+  IconBug,
+  IconChecklist,
+  IconError404,
+  IconHelp,
+  IconLayoutDashboard,
+  IconLock,
+  IconLockAccess,
+  IconMessages,
+  IconNotification,
+  IconPackages,
+  IconPalette,
+  IconServerOff,
+  IconSettings,
+  IconSwitchHorizontal,
+  IconTool,
+  IconUserCog,
+  IconUserOff,
+  IconUsers,
+} from '@tabler/icons-react'
+import { AudioWaveform, Command, GalleryVerticalEnd } from 'lucide-react'
+import {
+  Sidebar,
+  SidebarContent,
+  SidebarFooter,
+  SidebarHeader,
+  SidebarRail,
+  SidebarTrigger,
+} 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 { type SidebarData } from './types'
+
+// SideNav data
+const data: 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: '/dashboard',
+          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: '/',
+          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: '/',
+          icon: IconHelp,
+        },
+      ],
+    },
+  ],
+}
+
+export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
+  return (
+    <div className='relative'>
+      <Sidebar collapsible='icon' {...props}>
+        <SidebarHeader>
+          <TeamSwitcher teams={data.teams} />
+        </SidebarHeader>
+        <SidebarContent>
+          {data.navGroups.map((props) => (
+            <NavGroup key={props.title} {...props} />
+          ))}
+        </SidebarContent>
+        <SidebarFooter>
+          <NavUser user={data.user} />
+        </SidebarFooter>
+        <SidebarRail />
+      </Sidebar>
+      <SidebarTrigger className='absolute top-1/2 -right-4 z-50 rounded-full hidden md:inline-flex'>
+        <IconSwitchHorizontal />
+      </SidebarTrigger>
+    </div>
+  )
+}

+ 12 - 2
src/components/custom/layout.tsx → src/components/layout/index.tsx

@@ -1,5 +1,7 @@
 import * as React from 'react'
+import { IconMenuDeep } from '@tabler/icons-react'
 import { cn } from '@/lib/utils'
+import { SidebarTrigger } from '../ui/sidebar'
 
 const LayoutContext = React.createContext<{
   offset: number
@@ -48,7 +50,7 @@ interface HeaderProps extends React.HTMLAttributes<HTMLDivElement> {
 }
 
 const Header = React.forwardRef<HTMLDivElement, HeaderProps>(
-  ({ className, sticky, ...props }, ref) => {
+  ({ className, sticky, children, ...props }, ref) => {
     // Check if Layout.Header is used within Layout
     const contextVal = React.useContext(LayoutContext)
     if (contextVal === null) {
@@ -69,7 +71,15 @@ const Header = React.forwardRef<HTMLDivElement, HeaderProps>(
           className
         )}
         {...props}
-      />
+      >
+        {children}
+        <SidebarTrigger
+          variant='default'
+          className='md:hidden h-9 w-9 flex-none [&_svg]:size-5'
+        >
+          <IconMenuDeep />
+        </SidebarTrigger>
+      </div>
     )
   }
 )

+ 109 - 0
src/components/layout/nav-group.tsx

@@ -0,0 +1,109 @@
+import { ReactNode } from 'react'
+import { Link, useLocation } from '@tanstack/react-router'
+import { ChevronRight } from 'lucide-react'
+import {
+  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'
+import { NavItem, type NavGroup } from './types'
+
+export function NavGroup({ title, items }: NavGroup) {
+  const { setOpenMobile } = useSidebar()
+  const href = useLocation({ select: (location) => location.href })
+  return (
+    <SidebarGroup>
+      <SidebarGroupLabel>{title}</SidebarGroupLabel>
+      <SidebarMenu>
+        {items.map((item) => {
+          if (!item.items) {
+            return (
+              <SidebarMenuItem key={item.title}>
+                <SidebarMenuButton
+                  asChild
+                  isActive={checkIsActive(href, item)}
+                  tooltip={item.title}
+                >
+                  <Link to={item.url} onClick={() => setOpenMobile(false)}>
+                    {item.icon && <item.icon />}
+                    <span>{item.title}</span>
+                    {item.badge && <NavBadge>{item.badge}</NavBadge>}
+                  </Link>
+                </SidebarMenuButton>
+              </SidebarMenuItem>
+            )
+          }
+          return (
+            <Collapsible
+              key={item.title}
+              asChild
+              defaultOpen={checkIsActive(href, item, true)}
+              className='group/collapsible'
+            >
+              <SidebarMenuItem>
+                <CollapsibleTrigger asChild>
+                  <SidebarMenuButton tooltip={item.title}>
+                    {item.icon && <item.icon />}
+                    <span>{item.title}</span>
+                    {item.badge && <NavBadge>{item.badge}</NavBadge>}
+                    <ChevronRight className='ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90' />
+                  </SidebarMenuButton>
+                </CollapsibleTrigger>
+                <CollapsibleContent className='CollapsibleContent'>
+                  <SidebarMenuSub>
+                    {item.items.map((subItem) => (
+                      <SidebarMenuSubItem key={subItem.title}>
+                        <SidebarMenuSubButton
+                          asChild
+                          isActive={checkIsActive(href, subItem)}
+                        >
+                          <Link
+                            to={subItem.url}
+                            onClick={() => setOpenMobile(false)}
+                          >
+                            {subItem.icon && <subItem.icon />}
+                            <span>{subItem.title}</span>
+                            {subItem.badge && (
+                              <NavBadge>{subItem.badge}</NavBadge>
+                            )}
+                          </Link>
+                        </SidebarMenuSubButton>
+                      </SidebarMenuSubItem>
+                    ))}
+                  </SidebarMenuSub>
+                </CollapsibleContent>
+              </SidebarMenuItem>
+            </Collapsible>
+          )
+        })}
+      </SidebarMenu>
+    </SidebarGroup>
+  )
+}
+
+const NavBadge = ({ children }: { children: ReactNode }) => (
+  <Badge className='text-xs rounded-full px-1 py-0'>{children}</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])
+  )
+}

+ 107 - 0
src/components/layout/nav-user.tsx

@@ -0,0 +1,107 @@
+import {
+  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'
+import {
+  SidebarMenu,
+  SidebarMenuButton,
+  SidebarMenuItem,
+  useSidebar,
+} from '@/components/ui/sidebar'
+
+export function NavUser({
+  user,
+}: {
+  user: {
+    name: string
+    email: string
+    avatar: string
+  }
+}) {
+  const { isMobile } = useSidebar()
+
+  return (
+    <SidebarMenu>
+      <SidebarMenuItem>
+        <DropdownMenu>
+          <DropdownMenuTrigger asChild>
+            <SidebarMenuButton
+              size='lg'
+              className='data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground'
+            >
+              <Avatar className='h-8 w-8 rounded-lg'>
+                <AvatarImage src={user.avatar} alt={user.name} />
+                <AvatarFallback className='rounded-lg'>SN</AvatarFallback>
+              </Avatar>
+              <div className='grid flex-1 text-left text-sm leading-tight'>
+                <span className='truncate font-semibold'>{user.name}</span>
+                <span className='truncate text-xs'>{user.email}</span>
+              </div>
+              <ChevronsUpDown className='ml-auto size-4' />
+            </SidebarMenuButton>
+          </DropdownMenuTrigger>
+          <DropdownMenuContent
+            className='w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg'
+            side={isMobile ? 'bottom' : 'right'}
+            align='end'
+            sideOffset={4}
+          >
+            <DropdownMenuLabel className='p-0 font-normal'>
+              <div className='flex items-center gap-2 px-1 py-1.5 text-left text-sm'>
+                <Avatar className='h-8 w-8 rounded-lg'>
+                  <AvatarImage src={user.avatar} alt={user.name} />
+                  <AvatarFallback className='rounded-lg'>SN</AvatarFallback>
+                </Avatar>
+                <div className='grid flex-1 text-left text-sm leading-tight'>
+                  <span className='truncate font-semibold'>{user.name}</span>
+                  <span className='truncate text-xs'>{user.email}</span>
+                </div>
+              </div>
+            </DropdownMenuLabel>
+            <DropdownMenuSeparator />
+            <DropdownMenuGroup>
+              <DropdownMenuItem>
+                <Sparkles />
+                Upgrade to Pro
+              </DropdownMenuItem>
+            </DropdownMenuGroup>
+            <DropdownMenuSeparator />
+            <DropdownMenuGroup>
+              <DropdownMenuItem>
+                <BadgeCheck />
+                Account
+              </DropdownMenuItem>
+              <DropdownMenuItem>
+                <CreditCard />
+                Billing
+              </DropdownMenuItem>
+              <DropdownMenuItem>
+                <Bell />
+                Notifications
+              </DropdownMenuItem>
+            </DropdownMenuGroup>
+            <DropdownMenuSeparator />
+            <DropdownMenuItem>
+              <LogOut />
+              Log out
+            </DropdownMenuItem>
+          </DropdownMenuContent>
+        </DropdownMenu>
+      </SidebarMenuItem>
+    </SidebarMenu>
+  )
+}

+ 86 - 0
src/components/layout/team-switcher.tsx

@@ -0,0 +1,86 @@
+import * as React from 'react'
+import { ChevronsUpDown, Plus } from 'lucide-react'
+import {
+  DropdownMenu,
+  DropdownMenuContent,
+  DropdownMenuItem,
+  DropdownMenuLabel,
+  DropdownMenuSeparator,
+  DropdownMenuShortcut,
+  DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu'
+import {
+  SidebarMenu,
+  SidebarMenuButton,
+  SidebarMenuItem,
+  useSidebar,
+} from '@/components/ui/sidebar'
+
+export function TeamSwitcher({
+  teams,
+}: {
+  teams: {
+    name: string
+    logo: React.ElementType
+    plan: string
+  }[]
+}) {
+  const { isMobile } = useSidebar()
+  const [activeTeam, setActiveTeam] = React.useState(teams[0])
+
+  return (
+    <SidebarMenu>
+      <SidebarMenuItem>
+        <DropdownMenu>
+          <DropdownMenuTrigger asChild>
+            <SidebarMenuButton
+              size='lg'
+              className='data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground'
+            >
+              <div className='flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground'>
+                <activeTeam.logo className='size-4' />
+              </div>
+              <div className='grid flex-1 text-left text-sm leading-tight'>
+                <span className='truncate font-semibold'>
+                  {activeTeam.name}
+                </span>
+                <span className='truncate text-xs'>{activeTeam.plan}</span>
+              </div>
+              <ChevronsUpDown className='ml-auto' />
+            </SidebarMenuButton>
+          </DropdownMenuTrigger>
+          <DropdownMenuContent
+            className='w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg'
+            align='start'
+            side={isMobile ? 'bottom' : 'right'}
+            sideOffset={4}
+          >
+            <DropdownMenuLabel className='text-xs text-muted-foreground'>
+              Teams
+            </DropdownMenuLabel>
+            {teams.map((team, index) => (
+              <DropdownMenuItem
+                key={team.name}
+                onClick={() => setActiveTeam(team)}
+                className='gap-2 p-2'
+              >
+                <div className='flex size-6 items-center justify-center rounded-sm border'>
+                  <team.logo className='size-4 shrink-0' />
+                </div>
+                {team.name}
+                <DropdownMenuShortcut>⌘{index + 1}</DropdownMenuShortcut>
+              </DropdownMenuItem>
+            ))}
+            <DropdownMenuSeparator />
+            <DropdownMenuItem className='gap-2 p-2'>
+              <div className='flex size-6 items-center justify-center rounded-md border bg-background'>
+                <Plus className='size-4' />
+              </div>
+              <div className='font-medium text-muted-foreground'>Add team</div>
+            </DropdownMenuItem>
+          </DropdownMenuContent>
+        </DropdownMenu>
+      </SidebarMenuItem>
+    </SidebarMenu>
+  )
+}

+ 10 - 9
src/components/top-nav.tsx → src/components/layout/top-nav.tsx

@@ -1,25 +1,24 @@
+import { Link } from '@tanstack/react-router'
+import { IconMenu } from '@tabler/icons-react'
 import { cn } from '@/lib/utils'
-import { Link } from 'react-router-dom'
 import {
   DropdownMenu,
   DropdownMenuContent,
   DropdownMenuItem,
   DropdownMenuTrigger,
 } from '@/components/ui/dropdown-menu'
-import { Button } from './custom/button'
-import { IconMenu } from '@tabler/icons-react'
-import { useTranslations } from 'use-intl'
+import { Button } from '@/components/button'
 
 interface TopNavProps extends React.HTMLAttributes<HTMLElement> {
   links: {
     title: string
     href: string
     isActive: boolean
+    disabled?: boolean
   }[]
 }
 
 export function TopNav({ className, links, ...props }: TopNavProps) {
-  const t = useTranslations()
   return (
     <>
       <div className='md:hidden'>
@@ -30,13 +29,14 @@ export function TopNav({ className, links, ...props }: TopNavProps) {
             </Button>
           </DropdownMenuTrigger>
           <DropdownMenuContent side='bottom' align='start'>
-            {links.map(({ title, href, isActive }) => (
+            {links.map(({ title, href, isActive, disabled }) => (
               <DropdownMenuItem key={`${title}-${href}`} asChild>
                 <Link
                   to={href}
                   className={!isActive ? 'text-muted-foreground' : ''}
+                  disabled={disabled}
                 >
-                  {t(title)}
+                  {title}
                 </Link>
               </DropdownMenuItem>
             ))}
@@ -51,13 +51,14 @@ export function TopNav({ className, links, ...props }: TopNavProps) {
         )}
         {...props}
       >
-        {links.map(({ title, href, isActive }) => (
+        {links.map(({ title, href, isActive, disabled }) => (
           <Link
             key={`${title}-${href}`}
             to={href}
+            disabled={disabled}
             className={`text-sm font-medium transition-colors hover:text-primary ${isActive ? '' : 'text-muted-foreground'}`}
           >
-            {t(title)}
+            {title}
           </Link>
         ))}
       </nav>

+ 42 - 0
src/components/layout/types.ts

@@ -0,0 +1,42 @@
+import { LinkProps } from '@tanstack/react-router'
+
+interface User {
+  name: string
+  email: string
+  avatar: string
+}
+
+interface Team {
+  name: string
+  logo: React.ElementType
+  plan: string
+}
+
+interface BaseNavItem {
+  title: string
+  badge?: string
+  icon?: React.ElementType
+}
+
+export type NavItem =
+  | (BaseNavItem & {
+      items: (BaseNavItem & { url: LinkProps['to'] })[]
+      url?: never
+    })
+  | (BaseNavItem & {
+      url: LinkProps['to']
+      items?: never
+    })
+
+interface NavGroup {
+  title: string
+  items: NavItem[]
+}
+
+interface SidebarData {
+  user: User
+  teams: Team[]
+  navGroups: NavGroup[]
+}
+
+export type { SidebarData, NavGroup }

+ 0 - 10
src/components/loader.tsx

@@ -1,10 +0,0 @@
-import { IconLoader } from '@tabler/icons-react'
-
-export default function Loader() {
-  return (
-    <div className='flex h-svh w-full items-center justify-center'>
-      <IconLoader className='animate-spin' size={32} />
-      <span className='sr-only'>loading</span>
-    </div>
-  )
-}

+ 0 - 243
src/components/nav.tsx

@@ -1,243 +0,0 @@
-import { Link } from 'react-router-dom'
-import { IconChevronDown } from '@tabler/icons-react'
-import { Button, buttonVariants } from './custom/button'
-import {
-  Collapsible,
-  CollapsibleContent,
-  CollapsibleTrigger,
-} from './ui/collapsible'
-import {
-  DropdownMenu,
-  DropdownMenuContent,
-  DropdownMenuItem,
-  DropdownMenuLabel,
-  DropdownMenuSeparator,
-  DropdownMenuTrigger,
-} from './ui/dropdown-menu'
-import {
-  Tooltip,
-  TooltipContent,
-  TooltipProvider,
-  TooltipTrigger,
-} from './ui/tooltip'
-import { cn } from '@/lib/utils'
-import useCheckActiveNav from '@/hooks/use-check-active-nav'
-import { SideLink } from '@/data/sidelinks'
-import { useTranslations } from 'use-intl'
-
-interface NavProps extends React.HTMLAttributes<HTMLDivElement> {
-  isCollapsed: boolean
-  links: SideLink[]
-  closeNav: () => void
-}
-
-export default function Nav({
-  links,
-  isCollapsed,
-  className,
-  closeNav,
-}: NavProps) {
-  const renderLink = ({ sub, ...rest }: SideLink) => {
-    const key = `${rest.title}-${rest.href}`
-    if (isCollapsed && sub)
-      return (
-        <NavLinkIconDropdown
-          {...rest}
-          sub={sub}
-          key={key}
-          closeNav={closeNav}
-        />
-      )
-
-    if (isCollapsed)
-      return <NavLinkIcon {...rest} key={key} closeNav={closeNav} />
-
-    if (sub)
-      return (
-        <NavLinkDropdown {...rest} sub={sub} key={key} closeNav={closeNav} />
-      )
-
-    return <NavLink {...rest} key={key} closeNav={closeNav} />
-  }
-  return (
-    <div
-      data-collapsed={isCollapsed}
-      className={cn(
-        'group border-b bg-background py-2 transition-[max-height,padding] duration-500 data-[collapsed=true]:py-2 md:border-none',
-        className
-      )}
-    >
-      <TooltipProvider delayDuration={0}>
-        <nav className='grid gap-1 group-[[data-collapsed=true]]:justify-center group-[[data-collapsed=true]]:px-2'>
-          {links.map(renderLink)}
-        </nav>
-      </TooltipProvider>
-    </div>
-  )
-}
-
-interface NavLinkProps extends SideLink {
-  subLink?: boolean
-  closeNav: () => void
-}
-
-function NavLink({
-  title,
-  icon,
-  label,
-  href,
-  closeNav,
-  subLink = false,
-}: NavLinkProps) {
-  const { checkActiveNav } = useCheckActiveNav()
-  const t = useTranslations()
-  return (
-    <Link
-      to={href}
-      onClick={closeNav}
-      className={cn(
-        buttonVariants({
-          variant: checkActiveNav(href) ? 'secondary' : 'ghost',
-          size: 'sm',
-        }),
-        'h-12 justify-start text-wrap rounded-none px-6',
-        subLink && 'h-10 w-full border-l border-l-slate-500 px-2'
-      )}
-      aria-current={checkActiveNav(href) ? 'page' : undefined}
-    >
-      <div className='mr-2'>{icon}</div>
-      {t(title)}
-      {label && (
-        <div className='ml-2 rounded-lg bg-primary px-1 text-[0.625rem] text-primary-foreground'>
-          {label}
-        </div>
-      )}
-    </Link>
-  )
-}
-
-function NavLinkDropdown({ title, icon, label, sub, closeNav }: NavLinkProps) {
-  const { checkActiveNav } = useCheckActiveNav()
-  const t = useTranslations()
-
-  /* Open collapsible by default
-   * if one of child element is active */
-  const isChildActive = !!sub?.find((s) => checkActiveNav(s.href))
-
-  return (
-    <Collapsible defaultOpen={isChildActive}>
-      <CollapsibleTrigger
-        className={cn(
-          buttonVariants({ variant: 'ghost', size: 'sm' }),
-          'group h-12 w-full justify-start rounded-none px-6'
-        )}
-      >
-        <div className='mr-2'>{icon}</div>
-        {t(title)}
-        {label && (
-          <div className='ml-2 rounded-lg bg-primary px-1 text-[0.625rem] text-primary-foreground'>
-            {label}
-          </div>
-        )}
-        <span
-          className={cn(
-            'ml-auto transition-all group-data-[state="open"]:-rotate-180'
-          )}
-        >
-          <IconChevronDown stroke={1} />
-        </span>
-      </CollapsibleTrigger>
-      <CollapsibleContent className='collapsibleDropdown' asChild>
-        <ul>
-          {sub!.map((sublink) => (
-            <li key={sublink.title} className='my-1 ml-8'>
-              <NavLink {...sublink} subLink closeNav={closeNav} />
-            </li>
-          ))}
-        </ul>
-      </CollapsibleContent>
-    </Collapsible>
-  )
-}
-
-function NavLinkIcon({ title, icon, label, href }: NavLinkProps) {
-  const { checkActiveNav } = useCheckActiveNav()
-  return (
-    <Tooltip delayDuration={0}>
-      <TooltipTrigger asChild>
-        <Link
-          to={href}
-          className={cn(
-            buttonVariants({
-              variant: checkActiveNav(href) ? 'secondary' : 'ghost',
-              size: 'icon',
-            }),
-            'h-12 w-12'
-          )}
-        >
-          {icon}
-          <span className='sr-only'>{title}</span>
-        </Link>
-      </TooltipTrigger>
-      <TooltipContent side='right' className='flex items-center gap-4'>
-        {title}
-        {label && (
-          <span className='ml-auto text-muted-foreground'>{label}</span>
-        )}
-      </TooltipContent>
-    </Tooltip>
-  )
-}
-
-function NavLinkIconDropdown({ title, icon, label, sub }: NavLinkProps) {
-  const { checkActiveNav } = useCheckActiveNav()
-
-  /* Open collapsible by default
-   * if one of child element is active */
-  const isChildActive = !!sub?.find((s) => checkActiveNav(s.href))
-
-  return (
-    <DropdownMenu>
-      <Tooltip delayDuration={0}>
-        <TooltipTrigger asChild>
-          <DropdownMenuTrigger asChild>
-            <Button
-              variant={isChildActive ? 'secondary' : 'ghost'}
-              size='icon'
-              className='h-12 w-12'
-            >
-              {icon}
-            </Button>
-          </DropdownMenuTrigger>
-        </TooltipTrigger>
-        <TooltipContent side='right' className='flex items-center gap-4'>
-          {title}{' '}
-          {label && (
-            <span className='ml-auto text-muted-foreground'>{label}</span>
-          )}
-          <IconChevronDown
-            size={18}
-            className='-rotate-90 text-muted-foreground'
-          />
-        </TooltipContent>
-      </Tooltip>
-      <DropdownMenuContent side='right' align='start' sideOffset={4}>
-        <DropdownMenuLabel>
-          {title} {label ? `(${label})` : ''}
-        </DropdownMenuLabel>
-        <DropdownMenuSeparator />
-        {sub!.map(({ title, icon, label, href }) => (
-          <DropdownMenuItem key={`${title}-${href}`} asChild>
-            <Link
-              to={href}
-              className={`${checkActiveNav(href) ? 'bg-secondary' : ''}`}
-            >
-              {icon} <span className='ml-2 max-w-52 text-wrap'>{title}</span>
-              {label && <span className='ml-auto text-xs'>{label}</span>}
-            </Link>
-          </DropdownMenuItem>
-        ))}
-      </DropdownMenuContent>
-    </DropdownMenu>
-  )
-}

+ 5 - 3
src/components/custom/password-input.tsx → src/components/password-input.tsx

@@ -1,10 +1,12 @@
 import * as React from 'react'
 import { IconEye, IconEyeOff } from '@tabler/icons-react'
-import { Button } from './button'
 import { cn } from '@/lib/utils'
+import { Button } from './button'
 
-export interface PasswordInputProps
-  extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type'> {}
+type PasswordInputProps = Omit<
+  React.InputHTMLAttributes<HTMLInputElement>,
+  'type'
+>
 
 const PasswordInput = React.forwardRef<HTMLInputElement, PasswordInputProps>(
   ({ className, ...props }, ref) => {

+ 0 - 0
src/components/custom/pin-input.tsx → src/components/pin-input.tsx


+ 7 - 9
src/components/user-nav.tsx → src/components/profile-dropdown.tsx

@@ -1,5 +1,4 @@
 import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
-import { Button } from '@/components/custom/button'
 import {
   DropdownMenu,
   DropdownMenuContent,
@@ -10,10 +9,9 @@ import {
   DropdownMenuShortcut,
   DropdownMenuTrigger,
 } from '@/components/ui/dropdown-menu'
-import { useTranslations } from 'use-intl'
+import { Button } from '@/components/button'
 
-export function UserNav() {
-  const t = useTranslations('userNav')
+export function ProfileDropdown() {
   return (
     <DropdownMenu>
       <DropdownMenuTrigger asChild>
@@ -36,22 +34,22 @@ export function UserNav() {
         <DropdownMenuSeparator />
         <DropdownMenuGroup>
           <DropdownMenuItem>
-            {t('profile')}
+            Profile
             <DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
           </DropdownMenuItem>
           <DropdownMenuItem>
-            {t('billing')}
+            Billing
             <DropdownMenuShortcut>⌘B</DropdownMenuShortcut>
           </DropdownMenuItem>
           <DropdownMenuItem>
-            {t('settings')}
+            Settings
             <DropdownMenuShortcut>⌘S</DropdownMenuShortcut>
           </DropdownMenuItem>
-          <DropdownMenuItem>{t('new_team')}</DropdownMenuItem>
+          <DropdownMenuItem>New Team</DropdownMenuItem>
         </DropdownMenuGroup>
         <DropdownMenuSeparator />
         <DropdownMenuItem>
-          {t('log_out')}
+          Log out
           <DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>
         </DropdownMenuItem>
       </DropdownMenuContent>

+ 1 - 3
src/components/search.tsx

@@ -1,13 +1,11 @@
 import { Input } from '@/components/ui/input'
-import { useTranslations } from 'use-intl'
 
 export function Search() {
-  const t = useTranslations('dashboard')
   return (
     <div>
       <Input
         type='search'
-        placeholder={t('search')}
+        placeholder='Search'
         className='md:w-[100px] lg:w-[300px]'
       />
     </div>

+ 0 - 126
src/components/sidebar.tsx

@@ -1,126 +0,0 @@
-import { useEffect, useState } from 'react'
-import { IconChevronsLeft, IconMenu2, IconX } from '@tabler/icons-react'
-import { Layout } from './custom/layout'
-import { Button } from './custom/button'
-import Nav from './nav'
-import { cn } from '@/lib/utils'
-import { sidelinks } from '@/data/sidelinks'
-
-interface SidebarProps extends React.HTMLAttributes<HTMLElement> {
-  isCollapsed: boolean
-  setIsCollapsed: React.Dispatch<React.SetStateAction<boolean>>
-}
-
-export default function Sidebar({
-  className,
-  isCollapsed,
-  setIsCollapsed,
-}: SidebarProps) {
-  const [navOpened, setNavOpened] = useState(false)
-
-  /* Make body not scrollable when navBar is opened */
-  useEffect(() => {
-    if (navOpened) {
-      document.body.classList.add('overflow-hidden')
-    } else {
-      document.body.classList.remove('overflow-hidden')
-    }
-  }, [navOpened])
-
-  return (
-    <aside
-      className={cn(
-        `fixed left-0 right-0 top-0 z-50 w-full border-r-2 border-r-muted transition-[width] md:bottom-0 md:right-auto md:h-svh ${isCollapsed ? 'md:w-14' : 'md:w-64'}`,
-        className
-      )}
-    >
-      {/* Overlay in mobile */}
-      <div
-        onClick={() => setNavOpened(false)}
-        className={`absolute inset-0 transition-[opacity] delay-100 duration-700 ${navOpened ? 'h-svh opacity-50' : 'h-0 opacity-0'} w-full bg-black md:hidden`}
-      />
-
-      <Layout fixed className={navOpened ? 'h-svh' : ''}>
-        {/* Header */}
-        <Layout.Header
-          sticky
-          className='z-50 flex justify-between px-4 py-3 shadow-sm md:px-4'
-        >
-          <div className={`flex items-center ${!isCollapsed ? 'gap-2' : ''}`}>
-            <svg
-              xmlns='http://www.w3.org/2000/svg'
-              viewBox='0 0 256 256'
-              className={`transition-all ${isCollapsed ? 'h-6 w-6' : 'h-8 w-8'}`}
-            >
-              <rect width='256' height='256' fill='none'></rect>
-              <line
-                x1='208'
-                y1='128'
-                x2='128'
-                y2='208'
-                fill='none'
-                stroke='currentColor'
-                strokeLinecap='round'
-                strokeLinejoin='round'
-                strokeWidth='16'
-              ></line>
-              <line
-                x1='192'
-                y1='40'
-                x2='40'
-                y2='192'
-                fill='none'
-                stroke='currentColor'
-                strokeLinecap='round'
-                strokeLinejoin='round'
-                strokeWidth='16'
-              ></line>
-              <span className='sr-only'>Website Name</span>
-            </svg>
-            <div
-              className={`flex flex-col justify-end truncate ${isCollapsed ? 'invisible w-0' : 'visible w-auto'}`}
-            >
-              <span className='font-medium'>Shadcn Admin</span>
-              <span className='text-xs'>Vite + ShadcnUI</span>
-            </div>
-          </div>
-
-          {/* Toggle Button in mobile */}
-          <Button
-            variant='ghost'
-            size='icon'
-            className='md:hidden'
-            aria-label='Toggle Navigation'
-            aria-controls='sidebar-menu'
-            aria-expanded={navOpened}
-            onClick={() => setNavOpened((prev) => !prev)}
-          >
-            {navOpened ? <IconX /> : <IconMenu2 />}
-          </Button>
-        </Layout.Header>
-
-        {/* Navigation links */}
-        <Nav
-          id='sidebar-menu'
-          className={`z-40 h-full flex-1 overflow-auto ${navOpened ? 'max-h-screen' : 'max-h-0 py-0 md:max-h-screen md:py-2'}`}
-          closeNav={() => setNavOpened(false)}
-          isCollapsed={isCollapsed}
-          links={sidelinks}
-        />
-
-        {/* Scrollbar width toggle button */}
-        <Button
-          onClick={() => setIsCollapsed((prev) => !prev)}
-          size='icon'
-          variant='outline'
-          className='absolute -right-5 top-1/2 z-50 hidden rounded-full md:inline-flex'
-        >
-          <IconChevronsLeft
-            stroke={1.5}
-            className={`h-5 w-5 ${isCollapsed ? 'rotate-180' : ''}`}
-          />
-        </Button>
-      </Layout>
-    </aside>
-  )
-}

+ 4 - 6
src/components/theme-switch.tsx

@@ -1,5 +1,6 @@
 import { useEffect } from 'react'
 import { IconCheck, IconMoon, IconSun } from '@tabler/icons-react'
+import { useTheme } from '@/context/theme-context'
 import { cn } from '@/lib/utils'
 import {
   DropdownMenu,
@@ -7,10 +8,9 @@ import {
   DropdownMenuItem,
   DropdownMenuTrigger,
 } from '@/components/ui/dropdown-menu'
-import { useTheme } from './theme-provider'
-import { Button } from './custom/button'
+import { Button } from '@/components/button'
 
-export default function ThemeSwitch() {
+export function ThemeSwitch() {
   const { theme, setTheme } = useTheme()
 
   /* Update theme-color meta tag
@@ -18,11 +18,9 @@ export default function ThemeSwitch() {
   useEffect(() => {
     const themeColor = theme === 'dark' ? '#020817' : '#fff'
     const metaThemeColor = document.querySelector("meta[name='theme-color']")
-    metaThemeColor && metaThemeColor.setAttribute('content', themeColor)
+    if (metaThemeColor) metaThemeColor.setAttribute('content', themeColor)
   }, [theme])
 
-  console.log(theme)
-
   return (
     <DropdownMenu>
       <DropdownMenuTrigger asChild>

+ 0 - 1
src/components/ui/avatar.tsx

@@ -1,6 +1,5 @@
 import * as React from 'react'
 import * as AvatarPrimitive from '@radix-ui/react-avatar'
-
 import { cn } from '@/lib/utils'
 
 const Avatar = React.forwardRef<

+ 0 - 1
src/components/ui/badge.tsx

@@ -1,6 +1,5 @@
 import * as React from 'react'
 import { cva, type VariantProps } from 'class-variance-authority'
-
 import { cn } from '@/lib/utils'
 
 const badgeVariants = cva(

+ 56 - 0
src/components/ui/button.tsx

@@ -0,0 +1,56 @@
+import * as React from 'react'
+import { Slot } from '@radix-ui/react-slot'
+import { cva, type VariantProps } from 'class-variance-authority'
+import { cn } from '@/lib/utils'
+
+const buttonVariants = cva(
+  'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
+  {
+    variants: {
+      variant: {
+        default:
+          'bg-primary text-primary-foreground shadow hover:bg-primary/90',
+        destructive:
+          'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
+        outline:
+          'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
+        secondary:
+          'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
+        ghost: 'hover:bg-accent hover:text-accent-foreground',
+        link: 'text-primary underline-offset-4 hover:underline',
+      },
+      size: {
+        default: 'h-9 px-4 py-2',
+        sm: 'h-8 rounded-md px-3 text-xs',
+        lg: 'h-10 rounded-md px-8',
+        icon: 'h-9 w-9',
+      },
+    },
+    defaultVariants: {
+      variant: 'default',
+      size: 'default',
+    },
+  }
+)
+
+export interface ButtonProps
+  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
+    VariantProps<typeof buttonVariants> {
+  asChild?: boolean
+}
+
+const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
+  ({ className, variant, size, asChild = false, ...props }, ref) => {
+    const Comp = asChild ? Slot : 'button'
+    return (
+      <Comp
+        className={cn(buttonVariants({ variant, size, className }))}
+        ref={ref}
+        {...props}
+      />
+    )
+  }
+)
+Button.displayName = 'Button'
+
+export { Button, buttonVariants }

+ 5 - 6
src/components/ui/calendar.tsx

@@ -1,9 +1,8 @@
 import * as React from 'react'
-import { ChevronLeftIcon, ChevronRightIcon } from '@radix-ui/react-icons'
+import { ChevronLeft, ChevronRight } from 'lucide-react'
 import { DayPicker } from 'react-day-picker'
-
 import { cn } from '@/lib/utils'
-import { buttonVariants } from '@/components/custom/button'
+import { buttonVariants } from '@/components/ui/button'
 
 export type CalendarProps = React.ComponentProps<typeof DayPicker>
 
@@ -50,7 +49,7 @@ function Calendar({
           'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground',
         day_today: 'bg-accent text-accent-foreground',
         day_outside:
-          'day-outside text-muted-foreground opacity-50  aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30',
+          'day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground',
         day_disabled: 'text-muted-foreground opacity-50',
         day_range_middle:
           'aria-selected:bg-accent aria-selected:text-accent-foreground',
@@ -58,8 +57,8 @@ function Calendar({
         ...classNames,
       }}
       components={{
-        IconLeft: () => <ChevronLeftIcon className='h-4 w-4' />,
-        IconRight: () => <ChevronRightIcon className='h-4 w-4' />,
+        IconLeft: () => <ChevronLeft className='h-4 w-4' />,
+        IconRight: () => <ChevronRight className='h-4 w-4' />,
       }}
       {...props}
     />

+ 6 - 7
src/components/ui/card.tsx

@@ -1,5 +1,4 @@
 import * as React from 'react'
-
 import { cn } from '@/lib/utils'
 
 const Card = React.forwardRef<
@@ -30,10 +29,10 @@ const CardHeader = React.forwardRef<
 CardHeader.displayName = 'CardHeader'
 
 const CardTitle = React.forwardRef<
-  HTMLParagraphElement,
-  React.HTMLAttributes<HTMLHeadingElement>
+  HTMLDivElement,
+  React.HTMLAttributes<HTMLDivElement>
 >(({ className, ...props }, ref) => (
-  <h3
+  <div
     ref={ref}
     className={cn('font-semibold leading-none tracking-tight', className)}
     {...props}
@@ -42,10 +41,10 @@ const CardTitle = React.forwardRef<
 CardTitle.displayName = 'CardTitle'
 
 const CardDescription = React.forwardRef<
-  HTMLParagraphElement,
-  React.HTMLAttributes<HTMLParagraphElement>
+  HTMLDivElement,
+  React.HTMLAttributes<HTMLDivElement>
 >(({ className, ...props }, ref) => (
-  <p
+  <div
     ref={ref}
     className={cn('text-sm text-muted-foreground', className)}
     {...props}

+ 2 - 3
src/components/ui/checkbox.tsx

@@ -1,7 +1,6 @@
 import * as React from 'react'
 import * as CheckboxPrimitive from '@radix-ui/react-checkbox'
-import { CheckIcon } from '@radix-ui/react-icons'
-
+import { Check } from 'lucide-react'
 import { cn } from '@/lib/utils'
 
 const Checkbox = React.forwardRef<
@@ -19,7 +18,7 @@ const Checkbox = React.forwardRef<
     <CheckboxPrimitive.Indicator
       className={cn('flex items-center justify-center text-current')}
     >
-      <CheckIcon className='h-4 w-4' />
+      <Check className='h-4 w-4' />
     </CheckboxPrimitive.Indicator>
   </CheckboxPrimitive.Root>
 ))

+ 4 - 7
src/components/ui/command.tsx

@@ -1,8 +1,7 @@
 import * as React from 'react'
 import { type DialogProps } from '@radix-ui/react-dialog'
-import { MagnifyingGlassIcon } from '@radix-ui/react-icons'
 import { Command as CommandPrimitive } from 'cmdk'
-
+import { Search } from 'lucide-react'
 import { cn } from '@/lib/utils'
 import { Dialog, DialogContent } from '@/components/ui/dialog'
 
@@ -21,9 +20,7 @@ const Command = React.forwardRef<
 ))
 Command.displayName = CommandPrimitive.displayName
 
-interface CommandDialogProps extends DialogProps {}
-
-const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
+const CommandDialog = ({ children, ...props }: DialogProps) => {
   return (
     <Dialog {...props}>
       <DialogContent className='overflow-hidden p-0'>
@@ -40,7 +37,7 @@ const CommandInput = React.forwardRef<
   React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
 >(({ className, ...props }, ref) => (
   <div className='flex items-center border-b px-3' cmdk-input-wrapper=''>
-    <MagnifyingGlassIcon className='mr-2 h-4 w-4 shrink-0 opacity-50' />
+    <Search className='mr-2 h-4 w-4 shrink-0 opacity-50' />
     <CommandPrimitive.Input
       ref={ref}
       className={cn(
@@ -115,7 +112,7 @@ const CommandItem = React.forwardRef<
   <CommandPrimitive.Item
     ref={ref}
     className={cn(
-      'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
+      'relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
       className
     )}
     {...props}

+ 4 - 3
src/components/ui/dialog.tsx

@@ -1,7 +1,8 @@
+'use client'
+
 import * as React from 'react'
 import * as DialogPrimitive from '@radix-ui/react-dialog'
-import { Cross2Icon } from '@radix-ui/react-icons'
-
+import { X } from 'lucide-react'
 import { cn } from '@/lib/utils'
 
 const Dialog = DialogPrimitive.Root
@@ -43,7 +44,7 @@ const DialogContent = React.forwardRef<
     >
       {children}
       <DialogPrimitive.Close className='absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground'>
-        <Cross2Icon className='h-4 w-4' />
+        <X className='h-4 w-4' />
         <span className='sr-only'>Close</span>
       </DialogPrimitive.Close>
     </DialogPrimitive.Content>

+ 6 - 11
src/components/ui/dropdown-menu.tsx

@@ -1,11 +1,6 @@
 import * as React from 'react'
 import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
-import {
-  CheckIcon,
-  ChevronRightIcon,
-  DotFilledIcon,
-} from '@radix-ui/react-icons'
-
+import { Check, ChevronRight, Circle } from 'lucide-react'
 import { cn } from '@/lib/utils'
 
 const DropdownMenu = DropdownMenuPrimitive.Root
@@ -29,14 +24,14 @@ const DropdownMenuSubTrigger = React.forwardRef<
   <DropdownMenuPrimitive.SubTrigger
     ref={ref}
     className={cn(
-      'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent',
+      'flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
       inset && 'pl-8',
       className
     )}
     {...props}
   >
     {children}
-    <ChevronRightIcon className='ml-auto h-4 w-4' />
+    <ChevronRight className='ml-auto' />
   </DropdownMenuPrimitive.SubTrigger>
 ))
 DropdownMenuSubTrigger.displayName =
@@ -86,7 +81,7 @@ const DropdownMenuItem = React.forwardRef<
   <DropdownMenuPrimitive.Item
     ref={ref}
     className={cn(
-      'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
+      'relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0',
       inset && 'pl-8',
       className
     )}
@@ -110,7 +105,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
   >
     <span className='absolute left-2 flex h-3.5 w-3.5 items-center justify-center'>
       <DropdownMenuPrimitive.ItemIndicator>
-        <CheckIcon className='h-4 w-4' />
+        <Check className='h-4 w-4' />
       </DropdownMenuPrimitive.ItemIndicator>
     </span>
     {children}
@@ -133,7 +128,7 @@ const DropdownMenuRadioItem = React.forwardRef<
   >
     <span className='absolute left-2 flex h-3.5 w-3.5 items-center justify-center'>
       <DropdownMenuPrimitive.ItemIndicator>
-        <DotFilledIcon className='h-4 w-4 fill-current' />
+        <Circle className='h-2 w-2 fill-current' />
       </DropdownMenuPrimitive.ItemIndicator>
     </span>
     {children}

+ 2 - 3
src/components/ui/form.tsx

@@ -1,6 +1,4 @@
 import * as React from 'react'
-import * as LabelPrimitive from '@radix-ui/react-label'
-import { Slot } from '@radix-ui/react-slot'
 import {
   Controller,
   ControllerProps,
@@ -9,7 +7,8 @@ import {
   FormProvider,
   useFormContext,
 } from 'react-hook-form'
-
+import * as LabelPrimitive from '@radix-ui/react-label'
+import { Slot } from '@radix-ui/react-slot'
 import { cn } from '@/lib/utils'
 import { Label } from '@/components/ui/label'
 

+ 2 - 6
src/components/ui/input.tsx

@@ -1,17 +1,13 @@
 import * as React from 'react'
-
 import { cn } from '@/lib/utils'
 
-export interface InputProps
-  extends React.InputHTMLAttributes<HTMLInputElement> {}
-
-const Input = React.forwardRef<HTMLInputElement, InputProps>(
+const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<'input'>>(
   ({ className, type, ...props }, ref) => {
     return (
       <input
         type={type}
         className={cn(
-          'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
+          'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
           className
         )}
         ref={ref}

+ 0 - 1
src/components/ui/label.tsx

@@ -1,7 +1,6 @@
 import * as React from 'react'
 import * as LabelPrimitive from '@radix-ui/react-label'
 import { cva, type VariantProps } from 'class-variance-authority'
-
 import { cn } from '@/lib/utils'
 
 const labelVariants = cva(

+ 0 - 1
src/components/ui/popover.tsx

@@ -1,6 +1,5 @@
 import * as React from 'react'
 import * as PopoverPrimitive from '@radix-ui/react-popover'
-
 import { cn } from '@/lib/utils'
 
 const Popover = PopoverPrimitive.Root

+ 2 - 3
src/components/ui/radio-group.tsx

@@ -1,7 +1,6 @@
 import * as React from 'react'
-import { CheckIcon } from '@radix-ui/react-icons'
 import * as RadioGroupPrimitive from '@radix-ui/react-radio-group'
-
+import { Circle } from 'lucide-react'
 import { cn } from '@/lib/utils'
 
 const RadioGroup = React.forwardRef<
@@ -32,7 +31,7 @@ const RadioGroupItem = React.forwardRef<
       {...props}
     >
       <RadioGroupPrimitive.Indicator className='flex items-center justify-center'>
-        <CheckIcon className='h-3.5 w-3.5 fill-primary' />
+        <Circle className='h-3.5 w-3.5 fill-primary' />
       </RadioGroupPrimitive.Indicator>
     </RadioGroupPrimitive.Item>
   )

+ 5 - 11
src/components/ui/select.tsx

@@ -1,12 +1,6 @@
 import * as React from 'react'
-import {
-  CaretSortIcon,
-  CheckIcon,
-  ChevronDownIcon,
-  ChevronUpIcon,
-} from '@radix-ui/react-icons'
 import * as SelectPrimitive from '@radix-ui/react-select'
-
+import { Check, ChevronDown, ChevronUp } from 'lucide-react'
 import { cn } from '@/lib/utils'
 
 const Select = SelectPrimitive.Root
@@ -29,7 +23,7 @@ const SelectTrigger = React.forwardRef<
   >
     {children}
     <SelectPrimitive.Icon asChild>
-      <CaretSortIcon className='h-4 w-4 opacity-50' />
+      <ChevronDown className='h-4 w-4 opacity-50' />
     </SelectPrimitive.Icon>
   </SelectPrimitive.Trigger>
 ))
@@ -47,7 +41,7 @@ const SelectScrollUpButton = React.forwardRef<
     )}
     {...props}
   >
-    <ChevronUpIcon />
+    <ChevronUp className='h-4 w-4' />
   </SelectPrimitive.ScrollUpButton>
 ))
 SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
@@ -64,7 +58,7 @@ const SelectScrollDownButton = React.forwardRef<
     )}
     {...props}
   >
-    <ChevronDownIcon />
+    <ChevronDown className='h-4 w-4' />
   </SelectPrimitive.ScrollDownButton>
 ))
 SelectScrollDownButton.displayName =
@@ -128,7 +122,7 @@ const SelectItem = React.forwardRef<
   >
     <span className='absolute right-2 flex h-3.5 w-3.5 items-center justify-center'>
       <SelectPrimitive.ItemIndicator>
-        <CheckIcon className='h-4 w-4' />
+        <Check className='h-4 w-4' />
       </SelectPrimitive.ItemIndicator>
     </span>
     <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>

+ 0 - 1
src/components/ui/separator.tsx

@@ -1,6 +1,5 @@
 import * as React from 'react'
 import * as SeparatorPrimitive from '@radix-ui/react-separator'
-
 import { cn } from '@/lib/utils'
 
 const Separator = React.forwardRef<

+ 139 - 0
src/components/ui/sheet.tsx

@@ -0,0 +1,139 @@
+'use client'
+
+import * as React from 'react'
+import * as SheetPrimitive from '@radix-ui/react-dialog'
+import { cva, type VariantProps } from 'class-variance-authority'
+import { X } from 'lucide-react'
+import { cn } from '@/lib/utils'
+
+const Sheet = SheetPrimitive.Root
+
+const SheetTrigger = SheetPrimitive.Trigger
+
+const SheetClose = SheetPrimitive.Close
+
+const SheetPortal = SheetPrimitive.Portal
+
+const SheetOverlay = React.forwardRef<
+  React.ElementRef<typeof SheetPrimitive.Overlay>,
+  React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
+>(({ className, ...props }, ref) => (
+  <SheetPrimitive.Overlay
+    className={cn(
+      'fixed inset-0 z-50 bg-black/80  data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
+      className
+    )}
+    {...props}
+    ref={ref}
+  />
+))
+SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
+
+const sheetVariants = cva(
+  'fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out',
+  {
+    variants: {
+      side: {
+        top: 'inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
+        bottom:
+          'inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
+        left: 'inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm',
+        right:
+          'inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm',
+      },
+    },
+    defaultVariants: {
+      side: 'right',
+    },
+  }
+)
+
+interface SheetContentProps
+  extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
+    VariantProps<typeof sheetVariants> {}
+
+const SheetContent = React.forwardRef<
+  React.ElementRef<typeof SheetPrimitive.Content>,
+  SheetContentProps
+>(({ side = 'right', className, children, ...props }, ref) => (
+  <SheetPortal>
+    <SheetOverlay />
+    <SheetPrimitive.Content
+      ref={ref}
+      className={cn(sheetVariants({ side }), className)}
+      {...props}
+    >
+      <SheetPrimitive.Close className='absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary'>
+        <X className='h-4 w-4' />
+        <span className='sr-only'>Close</span>
+      </SheetPrimitive.Close>
+      {children}
+    </SheetPrimitive.Content>
+  </SheetPortal>
+))
+SheetContent.displayName = SheetPrimitive.Content.displayName
+
+const SheetHeader = ({
+  className,
+  ...props
+}: React.HTMLAttributes<HTMLDivElement>) => (
+  <div
+    className={cn(
+      'flex flex-col space-y-2 text-center sm:text-left',
+      className
+    )}
+    {...props}
+  />
+)
+SheetHeader.displayName = 'SheetHeader'
+
+const SheetFooter = ({
+  className,
+  ...props
+}: React.HTMLAttributes<HTMLDivElement>) => (
+  <div
+    className={cn(
+      'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
+      className
+    )}
+    {...props}
+  />
+)
+SheetFooter.displayName = 'SheetFooter'
+
+const SheetTitle = React.forwardRef<
+  React.ElementRef<typeof SheetPrimitive.Title>,
+  React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
+>(({ className, ...props }, ref) => (
+  <SheetPrimitive.Title
+    ref={ref}
+    className={cn('text-lg font-semibold text-foreground', className)}
+    {...props}
+  />
+))
+SheetTitle.displayName = SheetPrimitive.Title.displayName
+
+const SheetDescription = React.forwardRef<
+  React.ElementRef<typeof SheetPrimitive.Description>,
+  React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
+>(({ className, ...props }, ref) => (
+  <SheetPrimitive.Description
+    ref={ref}
+    className={cn('text-sm text-muted-foreground', className)}
+    {...props}
+  />
+))
+SheetDescription.displayName = SheetPrimitive.Description.displayName
+
+export {
+  Sheet,
+  SheetPortal,
+  SheetOverlay,
+  SheetTrigger,
+  SheetClose,
+  SheetContent,
+  SheetHeader,
+  SheetFooter,
+  SheetTitle,
+  SheetDescription,
+}

+ 768 - 0
src/components/ui/sidebar.tsx

@@ -0,0 +1,768 @@
+import * as React from 'react'
+import { Slot } from '@radix-ui/react-slot'
+import { VariantProps, cva } from 'class-variance-authority'
+import { cn } from '@/lib/utils'
+import { useIsMobile } from '@/hooks/use-mobile'
+import { Button } from '@/components/ui/button'
+import { Input } from '@/components/ui/input'
+import { Separator } from '@/components/ui/separator'
+import {
+  Sheet,
+  SheetContent,
+  SheetDescription,
+  SheetTitle,
+} from '@/components/ui/sheet'
+import { Skeleton } from '@/components/ui/skeleton'
+import {
+  Tooltip,
+  TooltipContent,
+  TooltipProvider,
+  TooltipTrigger,
+} from '@/components/ui/tooltip'
+
+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 SidebarContext = {
+  state: 'expanded' | 'collapsed'
+  open: boolean
+  setOpen: (open: boolean) => void
+  openMobile: boolean
+  setOpenMobile: (open: boolean) => void
+  isMobile: boolean
+  toggleSidebar: () => void
+}
+
+const SidebarContext = React.createContext<SidebarContext | null>(null)
+
+function useSidebar() {
+  const context = React.useContext(SidebarContext)
+  if (!context) {
+    throw new Error('useSidebar must be used within a SidebarProvider.')
+  }
+
+  return context
+}
+
+const SidebarProvider = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<'div'> & {
+    defaultOpen?: boolean
+    open?: boolean
+    onOpenChange?: (open: boolean) => void
+  }
+>(
+  (
+    {
+      defaultOpen = true,
+      open: openProp,
+      onOpenChange: setOpenProp,
+      className,
+      style,
+      children,
+      ...props
+    },
+    ref
+  ) => {
+    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 setOpen = React.useCallback(
+      (value: boolean | ((value: boolean) => boolean)) => {
+        const openState = typeof value === 'function' ? value(open) : value
+        if (setOpenProp) {
+          setOpenProp(openState)
+        } else {
+          _setOpen(openState)
+        }
+
+        // This sets the cookie to keep the sidebar state.
+        document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
+      },
+      [setOpenProp, open]
+    )
+
+    // Helper to toggle the sidebar.
+    const toggleSidebar = React.useCallback(() => {
+      return isMobile
+        ? setOpenMobile((open) => !open)
+        : setOpen((open) => !open)
+    }, [isMobile, setOpen, setOpenMobile])
+
+    // Adds a keyboard shortcut to toggle the sidebar.
+    React.useEffect(() => {
+      const handleKeyDown = (event: KeyboardEvent) => {
+        if (
+          event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
+          (event.metaKey || event.ctrlKey)
+        ) {
+          event.preventDefault()
+          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 contextValue = React.useMemo<SidebarContext>(
+      () => ({
+        state,
+        open,
+        setOpen,
+        isMobile,
+        openMobile,
+        setOpenMobile,
+        toggleSidebar,
+      }),
+      [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
+    )
+
+    return (
+      <SidebarContext.Provider value={contextValue}>
+        <TooltipProvider delayDuration={0}>
+          <div
+            style={
+              {
+                '--sidebar-width': SIDEBAR_WIDTH,
+                '--sidebar-width-icon': SIDEBAR_WIDTH_ICON,
+                ...style,
+              } as React.CSSProperties
+            }
+            className={cn(
+              'group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar',
+              className
+            )}
+            ref={ref}
+            {...props}
+          >
+            {children}
+          </div>
+        </TooltipProvider>
+      </SidebarContext.Provider>
+    )
+  }
+)
+SidebarProvider.displayName = 'SidebarProvider'
+
+const Sidebar = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<'div'> & {
+    side?: 'left' | 'right'
+    variant?: 'sidebar' | 'floating' | 'inset'
+    collapsible?: 'offcanvas' | 'icon' | 'none'
+  }
+>(
+  (
+    {
+      side = 'left',
+      variant = 'sidebar',
+      collapsible = 'offcanvas',
+      className,
+      children,
+      ...props
+    },
+    ref
+  ) => {
+    const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
+
+    if (collapsible === 'none') {
+      return (
+        <div
+          className={cn(
+            'flex h-full w-[--sidebar-width] flex-col bg-sidebar text-sidebar-foreground',
+            className
+          )}
+          ref={ref}
+          {...props}
+        >
+          {children}
+        </div>
+      )
+    }
+
+    if (isMobile) {
+      return (
+        <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
+          <SheetTitle className='sr-only'>Sidebar</SheetTitle>
+          <SheetContent
+            data-sidebar='sidebar'
+            data-mobile='true'
+            className='w-[--sidebar-width] bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden'
+            style={
+              {
+                '--sidebar-width': SIDEBAR_WIDTH_MOBILE,
+              } as React.CSSProperties
+            }
+            side={side}
+          >
+            <SheetDescription className='sr-only'>
+              Sidebar Content
+            </SheetDescription>
+            <div className='flex h-full w-full flex-col'>{children}</div>
+          </SheetContent>
+        </Sheet>
+      )
+    }
+
+    return (
+      <div
+        ref={ref}
+        className='group peer hidden md:block text-sidebar-foreground'
+        data-state={state}
+        data-collapsible={state === 'collapsed' ? collapsible : ''}
+        data-variant={variant}
+        data-side={side}
+      >
+        {/* This is what handles the sidebar gap on desktop */}
+        <div
+          className={cn(
+            'duration-200 relative h-svh w-[--sidebar-width] bg-transparent transition-[width] ease-linear',
+            'group-data-[collapsible=offcanvas]:w-0',
+            'group-data-[side=right]:rotate-180',
+            variant === 'floating' || variant === 'inset'
+              ? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]'
+              : 'group-data-[collapsible=icon]:w-[--sidebar-width-icon]'
+          )}
+        />
+        <div
+          className={cn(
+            'duration-200 fixed inset-y-0 z-10 hidden h-svh w-[--sidebar-width] transition-[left,right,width] ease-linear md:flex',
+            side === 'left'
+              ? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]'
+              : 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
+            // Adjust the padding for floating and inset variants.
+            variant === 'floating' || variant === 'inset'
+              ? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]'
+              : 'group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l',
+            className
+          )}
+          {...props}
+        >
+          <div
+            data-sidebar='sidebar'
+            className='flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow'
+          >
+            {children}
+          </div>
+        </div>
+      </div>
+    )
+  }
+)
+Sidebar.displayName = 'Sidebar'
+
+const SidebarTrigger = React.forwardRef<
+  React.ElementRef<typeof Button>,
+  React.ComponentProps<typeof Button>
+>(({ className, onClick, children, ...props }, ref) => {
+  const { toggleSidebar } = useSidebar()
+
+  return (
+    <Button
+      ref={ref}
+      data-sidebar='trigger'
+      variant='outline'
+      size='icon'
+      className={cn('h-7 w-7', className)}
+      onClick={(event) => {
+        onClick?.(event)
+        toggleSidebar()
+      }}
+      {...props}
+    >
+      {children}
+      <span className='sr-only'>Toggle Sidebar</span>
+    </Button>
+  )
+})
+SidebarTrigger.displayName = 'SidebarTrigger'
+
+const SidebarRail = React.forwardRef<
+  HTMLButtonElement,
+  React.ComponentProps<'button'>
+>(({ className, ...props }, ref) => {
+  const { toggleSidebar } = useSidebar()
+
+  return (
+    <button
+      ref={ref}
+      data-sidebar='rail'
+      aria-label='Toggle Sidebar'
+      tabIndex={-1}
+      onClick={toggleSidebar}
+      title='Toggle Sidebar'
+      className={cn(
+        'absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex',
+        '[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize',
+        '[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',
+        'group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar',
+        '[[data-side=left][data-collapsible=offcanvas]_&]:-right-2',
+        '[[data-side=right][data-collapsible=offcanvas]_&]:-left-2',
+        className
+      )}
+      {...props}
+    />
+  )
+})
+SidebarRail.displayName = 'SidebarRail'
+
+const SidebarInset = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<'main'>
+>(({ className, ...props }, ref) => {
+  return (
+    <main
+      ref={ref}
+      className={cn(
+        'relative flex min-h-svh flex-1 flex-col bg-background',
+        'peer-data-[variant=inset]:min-h-[calc(100svh-theme(spacing.4))] md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow',
+        className
+      )}
+      {...props}
+    />
+  )
+})
+SidebarInset.displayName = 'SidebarInset'
+
+const SidebarInput = React.forwardRef<
+  React.ElementRef<typeof Input>,
+  React.ComponentProps<typeof Input>
+>(({ className, ...props }, ref) => {
+  return (
+    <Input
+      ref={ref}
+      data-sidebar='input'
+      className={cn(
+        'h-8 w-full bg-background shadow-none focus-visible:ring-2 focus-visible:ring-sidebar-ring',
+        className
+      )}
+      {...props}
+    />
+  )
+})
+SidebarInput.displayName = 'SidebarInput'
+
+const SidebarHeader = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<'div'>
+>(({ className, ...props }, ref) => {
+  return (
+    <div
+      ref={ref}
+      data-sidebar='header'
+      className={cn('flex flex-col gap-2 p-2', className)}
+      {...props}
+    />
+  )
+})
+SidebarHeader.displayName = 'SidebarHeader'
+
+const SidebarFooter = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<'div'>
+>(({ className, ...props }, ref) => {
+  return (
+    <div
+      ref={ref}
+      data-sidebar='footer'
+      className={cn('flex flex-col gap-2 p-2', className)}
+      {...props}
+    />
+  )
+})
+SidebarFooter.displayName = 'SidebarFooter'
+
+const SidebarSeparator = React.forwardRef<
+  React.ElementRef<typeof Separator>,
+  React.ComponentProps<typeof Separator>
+>(({ className, ...props }, ref) => {
+  return (
+    <Separator
+      ref={ref}
+      data-sidebar='separator'
+      className={cn('mx-2 w-auto bg-sidebar-border', className)}
+      {...props}
+    />
+  )
+})
+SidebarSeparator.displayName = 'SidebarSeparator'
+
+const SidebarContent = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<'div'>
+>(({ className, ...props }, ref) => {
+  return (
+    <div
+      ref={ref}
+      data-sidebar='content'
+      className={cn(
+        'flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden',
+        className
+      )}
+      {...props}
+    />
+  )
+})
+SidebarContent.displayName = 'SidebarContent'
+
+const SidebarGroup = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<'div'>
+>(({ className, ...props }, ref) => {
+  return (
+    <div
+      ref={ref}
+      data-sidebar='group'
+      className={cn('relative flex w-full min-w-0 flex-col p-2', className)}
+      {...props}
+    />
+  )
+})
+SidebarGroup.displayName = 'SidebarGroup'
+
+const SidebarGroupLabel = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<'div'> & { asChild?: boolean }
+>(({ className, asChild = false, ...props }, ref) => {
+  const Comp = asChild ? Slot : 'div'
+
+  return (
+    <Comp
+      ref={ref}
+      data-sidebar='group-label'
+      className={cn(
+        'duration-200 flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opa] ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
+        'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',
+        className
+      )}
+      {...props}
+    />
+  )
+})
+SidebarGroupLabel.displayName = 'SidebarGroupLabel'
+
+const SidebarGroupAction = React.forwardRef<
+  HTMLButtonElement,
+  React.ComponentProps<'button'> & { asChild?: boolean }
+>(({ className, asChild = false, ...props }, ref) => {
+  const Comp = asChild ? Slot : 'button'
+
+  return (
+    <Comp
+      ref={ref}
+      data-sidebar='group-action'
+      className={cn(
+        'absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
+        // Increases the hit area of the button on mobile.
+        'after:absolute after:-inset-2 after:md:hidden',
+        'group-data-[collapsible=icon]:hidden',
+        className
+      )}
+      {...props}
+    />
+  )
+})
+SidebarGroupAction.displayName = 'SidebarGroupAction'
+
+const SidebarGroupContent = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<'div'>
+>(({ className, ...props }, ref) => (
+  <div
+    ref={ref}
+    data-sidebar='group-content'
+    className={cn('w-full text-sm', className)}
+    {...props}
+  />
+))
+SidebarGroupContent.displayName = 'SidebarGroupContent'
+
+const SidebarMenu = React.forwardRef<
+  HTMLUListElement,
+  React.ComponentProps<'ul'>
+>(({ className, ...props }, ref) => (
+  <ul
+    ref={ref}
+    data-sidebar='menu'
+    className={cn('flex w-full min-w-0 flex-col gap-1', className)}
+    {...props}
+  />
+))
+SidebarMenu.displayName = 'SidebarMenu'
+
+const SidebarMenuItem = React.forwardRef<
+  HTMLLIElement,
+  React.ComponentProps<'li'>
+>(({ className, ...props }, ref) => (
+  <li
+    ref={ref}
+    data-sidebar='menu-item'
+    className={cn('group/menu-item relative', className)}
+    {...props}
+  />
+))
+SidebarMenuItem.displayName = 'SidebarMenuItem'
+
+const sidebarMenuButtonVariants = cva(
+  'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0',
+  {
+    variants: {
+      variant: {
+        default: 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',
+        outline:
+          'bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]',
+      },
+      size: {
+        default: 'h-8 text-sm',
+        sm: 'h-7 text-xs',
+        lg: 'h-12 text-sm group-data-[collapsible=icon]:!p-0',
+      },
+    },
+    defaultVariants: {
+      variant: 'default',
+      size: 'default',
+    },
+  }
+)
+
+const SidebarMenuButton = React.forwardRef<
+  HTMLButtonElement,
+  React.ComponentProps<'button'> & {
+    asChild?: boolean
+    isActive?: boolean
+    tooltip?: string | React.ComponentProps<typeof TooltipContent>
+  } & VariantProps<typeof sidebarMenuButtonVariants>
+>(
+  (
+    {
+      asChild = false,
+      isActive = false,
+      variant = 'default',
+      size = 'default',
+      tooltip,
+      className,
+      ...props
+    },
+    ref
+  ) => {
+    const Comp = asChild ? Slot : 'button'
+    const { isMobile, state } = useSidebar()
+
+    const button = (
+      <Comp
+        ref={ref}
+        data-sidebar='menu-button'
+        data-size={size}
+        data-active={isActive}
+        className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
+        {...props}
+      />
+    )
+
+    if (!tooltip) {
+      return button
+    }
+
+    if (typeof tooltip === 'string') {
+      tooltip = {
+        children: tooltip,
+      }
+    }
+
+    return (
+      <Tooltip>
+        <TooltipTrigger asChild>{button}</TooltipTrigger>
+        <TooltipContent
+          side='right'
+          align='center'
+          hidden={state !== 'collapsed' || isMobile}
+          {...tooltip}
+        />
+      </Tooltip>
+    )
+  }
+)
+SidebarMenuButton.displayName = 'SidebarMenuButton'
+
+const SidebarMenuAction = React.forwardRef<
+  HTMLButtonElement,
+  React.ComponentProps<'button'> & {
+    asChild?: boolean
+    showOnHover?: boolean
+  }
+>(({ className, asChild = false, showOnHover = false, ...props }, ref) => {
+  const Comp = asChild ? Slot : 'button'
+
+  return (
+    <Comp
+      ref={ref}
+      data-sidebar='menu-action'
+      className={cn(
+        'absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0',
+        // Increases the hit area of the button on mobile.
+        'after:absolute after:-inset-2 after:md:hidden',
+        'peer-data-[size=sm]/menu-button:top-1',
+        'peer-data-[size=default]/menu-button:top-1.5',
+        'peer-data-[size=lg]/menu-button:top-2.5',
+        'group-data-[collapsible=icon]:hidden',
+        showOnHover &&
+          'group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0',
+        className
+      )}
+      {...props}
+    />
+  )
+})
+SidebarMenuAction.displayName = 'SidebarMenuAction'
+
+const SidebarMenuBadge = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<'div'>
+>(({ className, ...props }, ref) => (
+  <div
+    ref={ref}
+    data-sidebar='menu-badge'
+    className={cn(
+      'absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums text-sidebar-foreground select-none pointer-events-none',
+      'peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground',
+      'peer-data-[size=sm]/menu-button:top-1',
+      'peer-data-[size=default]/menu-button:top-1.5',
+      'peer-data-[size=lg]/menu-button:top-2.5',
+      'group-data-[collapsible=icon]:hidden',
+      className
+    )}
+    {...props}
+  />
+))
+SidebarMenuBadge.displayName = 'SidebarMenuBadge'
+
+const SidebarMenuSkeleton = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<'div'> & {
+    showIcon?: boolean
+  }
+>(({ className, showIcon = false, ...props }, ref) => {
+  // Random width between 50 to 90%.
+  const width = React.useMemo(() => {
+    return `${Math.floor(Math.random() * 40) + 50}%`
+  }, [])
+
+  return (
+    <div
+      ref={ref}
+      data-sidebar='menu-skeleton'
+      className={cn('rounded-md h-8 flex gap-2 px-2 items-center', className)}
+      {...props}
+    >
+      {showIcon && (
+        <Skeleton
+          className='size-4 rounded-md'
+          data-sidebar='menu-skeleton-icon'
+        />
+      )}
+      <Skeleton
+        className='h-4 flex-1 max-w-[--skeleton-width]'
+        data-sidebar='menu-skeleton-text'
+        style={
+          {
+            '--skeleton-width': width,
+          } as React.CSSProperties
+        }
+      />
+    </div>
+  )
+})
+SidebarMenuSkeleton.displayName = 'SidebarMenuSkeleton'
+
+const SidebarMenuSub = React.forwardRef<
+  HTMLUListElement,
+  React.ComponentProps<'ul'>
+>(({ className, ...props }, ref) => (
+  <ul
+    ref={ref}
+    data-sidebar='menu-sub'
+    className={cn(
+      'mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5',
+      'group-data-[collapsible=icon]:hidden',
+      className
+    )}
+    {...props}
+  />
+))
+SidebarMenuSub.displayName = 'SidebarMenuSub'
+
+const SidebarMenuSubItem = React.forwardRef<
+  HTMLLIElement,
+  React.ComponentProps<'li'>
+>(({ ...props }, ref) => <li ref={ref} {...props} />)
+SidebarMenuSubItem.displayName = 'SidebarMenuSubItem'
+
+const SidebarMenuSubButton = React.forwardRef<
+  HTMLAnchorElement,
+  React.ComponentProps<'a'> & {
+    asChild?: boolean
+    size?: 'sm' | 'md'
+    isActive?: boolean
+  }
+>(({ asChild = false, size = 'md', isActive, className, ...props }, ref) => {
+  const Comp = asChild ? Slot : 'a'
+
+  return (
+    <Comp
+      ref={ref}
+      data-sidebar='menu-sub-button'
+      data-size={size}
+      data-active={isActive}
+      className={cn(
+        'flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-none ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground',
+        'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground',
+        size === 'sm' && 'text-xs',
+        size === 'md' && 'text-sm',
+        'group-data-[collapsible=icon]:hidden',
+        className
+      )}
+      {...props}
+    />
+  )
+})
+SidebarMenuSubButton.displayName = 'SidebarMenuSubButton'
+
+export {
+  Sidebar,
+  SidebarContent,
+  SidebarFooter,
+  SidebarGroup,
+  SidebarGroupAction,
+  SidebarGroupContent,
+  SidebarGroupLabel,
+  SidebarHeader,
+  SidebarInput,
+  SidebarInset,
+  SidebarMenu,
+  SidebarMenuAction,
+  SidebarMenuBadge,
+  SidebarMenuButton,
+  SidebarMenuItem,
+  SidebarMenuSkeleton,
+  SidebarMenuSub,
+  SidebarMenuSubButton,
+  SidebarMenuSubItem,
+  SidebarProvider,
+  SidebarRail,
+  SidebarSeparator,
+  SidebarTrigger,
+  useSidebar,
+}

+ 15 - 0
src/components/ui/skeleton.tsx

@@ -0,0 +1,15 @@
+import { cn } from '@/lib/utils'
+
+function Skeleton({
+  className,
+  ...props
+}: React.HTMLAttributes<HTMLDivElement>) {
+  return (
+    <div
+      className={cn('animate-pulse rounded-md bg-primary/10', className)}
+      {...props}
+    />
+  )
+}
+
+export { Skeleton }

+ 0 - 1
src/components/ui/switch.tsx

@@ -1,6 +1,5 @@
 import * as React from 'react'
 import * as SwitchPrimitives from '@radix-ui/react-switch'
-
 import { cn } from '@/lib/utils'
 
 const Switch = React.forwardRef<

+ 0 - 1
src/components/ui/table.tsx

@@ -1,5 +1,4 @@
 import * as React from 'react'
-
 import { cn } from '@/lib/utils'
 
 const Table = React.forwardRef<

+ 0 - 1
src/components/ui/tabs.tsx

@@ -1,6 +1,5 @@
 import * as React from 'react'
 import * as TabsPrimitive from '@radix-ui/react-tabs'
-
 import { cn } from '@/lib/utils'
 
 const Tabs = TabsPrimitive.Root

+ 15 - 18
src/components/ui/textarea.tsx

@@ -1,24 +1,21 @@
 import * as React from 'react'
-
 import { cn } from '@/lib/utils'
 
-export interface TextareaProps
-  extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
-
-const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
-  ({ className, ...props }, ref) => {
-    return (
-      <textarea
-        className={cn(
-          'flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
-          className
-        )}
-        ref={ref}
-        {...props}
-      />
-    )
-  }
-)
+const Textarea = React.forwardRef<
+  HTMLTextAreaElement,
+  React.ComponentProps<'textarea'>
+>(({ className, ...props }, ref) => {
+  return (
+    <textarea
+      className={cn(
+        'flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
+        className
+      )}
+      ref={ref}
+      {...props}
+    />
+  )
+})
 Textarea.displayName = 'Textarea'
 
 export { Textarea }

+ 2 - 3
src/components/ui/toast.tsx

@@ -1,8 +1,7 @@
 import * as React from 'react'
-import { Cross2Icon } from '@radix-ui/react-icons'
 import * as ToastPrimitives from '@radix-ui/react-toast'
 import { cva, type VariantProps } from 'class-variance-authority'
-
+import { X } from 'lucide-react'
 import { cn } from '@/lib/utils'
 
 const ToastProvider = ToastPrimitives.Provider
@@ -81,7 +80,7 @@ const ToastClose = React.forwardRef<
     toast-close=''
     {...props}
   >
-    <Cross2Icon className='h-4 w-4' />
+    <X className='h-4 w-4' />
   </ToastPrimitives.Close>
 ))
 ToastClose.displayName = ToastPrimitives.Close.displayName

+ 1 - 1
src/components/ui/toaster.tsx

@@ -1,3 +1,4 @@
+import { useToast } from '@/hooks/use-toast'
 import {
   Toast,
   ToastClose,
@@ -6,7 +7,6 @@ import {
   ToastTitle,
   ToastViewport,
 } from '@/components/ui/toast'
-import { useToast } from '@/components/ui/use-toast'
 
 export function Toaster() {
   const { toasts } = useToast()

+ 11 - 10
src/components/ui/tooltip.tsx

@@ -1,6 +1,5 @@
 import * as React from 'react'
 import * as TooltipPrimitive from '@radix-ui/react-tooltip'
-
 import { cn } from '@/lib/utils'
 
 const TooltipProvider = TooltipPrimitive.Provider
@@ -13,15 +12,17 @@ const TooltipContent = React.forwardRef<
   React.ElementRef<typeof TooltipPrimitive.Content>,
   React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
 >(({ className, sideOffset = 4, ...props }, ref) => (
-  <TooltipPrimitive.Content
-    ref={ref}
-    sideOffset={sideOffset}
-    className={cn(
-      'z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-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',
-      className
-    )}
-    {...props}
-  />
+  <TooltipPrimitive.Portal>
+    <TooltipPrimitive.Content
+      ref={ref}
+      sideOffset={sideOffset}
+      className={cn(
+        'z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-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',
+        className
+      )}
+      {...props}
+    />
+  </TooltipPrimitive.Portal>
 ))
 TooltipContent.displayName = TooltipPrimitive.Content.displayName
 

+ 0 - 0
src/components/theme-provider.tsx → src/context/theme-context.tsx


+ 0 - 176
src/data/sidelinks.tsx

@@ -1,176 +0,0 @@
-import {
-  IconApps,
-  IconBarrierBlock,
-  IconBoxSeam,
-  IconChartHistogram,
-  IconChecklist,
-  IconComponents,
-  IconError404,
-  IconExclamationCircle,
-  IconHexagonNumber1,
-  IconHexagonNumber2,
-  IconHexagonNumber3,
-  IconHexagonNumber4,
-  IconHexagonNumber5,
-  IconLayoutDashboard,
-  IconMessages,
-  IconRouteAltLeft,
-  IconServerOff,
-  IconSettings,
-  IconTruck,
-  IconUserShield,
-  IconUsers,
-  IconLock,
-} from '@tabler/icons-react'
-
-export interface NavLink {
-  title: string
-  label?: string
-  href: string
-  icon: JSX.Element
-}
-
-export interface SideLink extends NavLink {
-  sub?: NavLink[]
-}
-
-export const sidelinks: SideLink[] = [
-  {
-    title: 'sidebar.dashboard',
-    label: '',
-    href: '/',
-    icon: <IconLayoutDashboard size={18} />,
-  },
-  {
-    title: 'sidebar.tasks',
-    label: '3',
-    href: '/tasks',
-    icon: <IconChecklist size={18} />,
-  },
-  {
-    title: 'sidebar.chats',
-    label: '9',
-    href: '/chats',
-    icon: <IconMessages size={18} />,
-  },
-  {
-    title: 'sidebar.apps',
-    label: '',
-    href: '/apps',
-    icon: <IconApps size={18} />,
-  },
-  {
-    title: 'sidebar.authentication',
-    label: '',
-    href: '',
-    icon: <IconUserShield size={18} />,
-    sub: [
-      {
-        title: 'sidebar.auth_sign_in_email_password',
-        label: '',
-        href: '/sign-in',
-        icon: <IconHexagonNumber1 size={18} />,
-      },
-      {
-        title: 'sidebar.auth_sign_in_box',
-        label: '',
-        href: '/sign-in-2',
-        icon: <IconHexagonNumber2 size={18} />,
-      },
-      {
-        title: 'sidebar.auth_sign_up',
-        label: '',
-        href: '/sign-up',
-        icon: <IconHexagonNumber3 size={18} />,
-      },
-      {
-        title: 'sidebar.auth_forgot_password',
-        label: '',
-        href: '/forgot-password',
-        icon: <IconHexagonNumber4 size={18} />,
-      },
-      {
-        title: 'sidebar.auth_otp',
-        label: '',
-        href: '/otp',
-        icon: <IconHexagonNumber5 size={18} />,
-      },
-    ],
-  },
-  {
-    title: 'sidebar.users',
-    label: '',
-    href: '/users',
-    icon: <IconUsers size={18} />,
-  },
-  {
-    title: 'sidebar.requests',
-    label: '10',
-    href: '/requests',
-    icon: <IconRouteAltLeft size={18} />,
-    sub: [
-      {
-        title: 'sidebar.requests_trucks',
-        label: '9',
-        href: '/trucks',
-        icon: <IconTruck size={18} />,
-      },
-      {
-        title: 'sidebar.requests_cargos',
-        label: '',
-        href: '/cargos',
-        icon: <IconBoxSeam size={18} />,
-      },
-    ],
-  },
-  {
-    title: 'sidebar.analytics',
-    label: '',
-    href: '/analysis',
-    icon: <IconChartHistogram size={18} />,
-  },
-  {
-    title: 'sidebar.extra_components',
-    label: '',
-    href: '/extra-components',
-    icon: <IconComponents size={18} />,
-  },
-  {
-    title: 'sidebar.error_pages',
-    label: '',
-    href: '',
-    icon: <IconExclamationCircle size={18} />,
-    sub: [
-      {
-        title: 'sidebar.error_not_found',
-        label: '',
-        href: '/404',
-        icon: <IconError404 size={18} />,
-      },
-      {
-        title: 'sidebar.error_internal_server',
-        label: '',
-        href: '/500',
-        icon: <IconServerOff size={18} />,
-      },
-      {
-        title: 'sidebar.error_maintenance',
-        label: '',
-        href: '/503',
-        icon: <IconBarrierBlock size={18} />,
-      },
-      {
-        title: 'sidebar.error_unauthorised',
-        label: '',
-        href: '/401',
-        icon: <IconLock size={18} />,
-      },
-    ],
-  },
-  {
-    title: 'sidebar.settings',
-    label: '',
-    href: '/settings',
-    icon: <IconSettings size={18} />,
-  },
-]

+ 0 - 0
src/pages/apps/data.tsx → src/features/apps/data/apps.tsx


+ 6 - 6
src/pages/apps/index.tsx → src/features/apps/index.tsx

@@ -4,7 +4,6 @@ import {
   IconSortAscendingLetters,
   IconSortDescendingLetters,
 } from '@tabler/icons-react'
-import { Layout } from '@/components/custom/layout'
 import { Input } from '@/components/ui/input'
 import {
   Select,
@@ -14,11 +13,12 @@ import {
   SelectValue,
 } from '@/components/ui/select'
 import { Separator } from '@/components/ui/separator'
+import { Button } from '@/components/button'
+import { Layout } from '@/components/layout'
+import { ProfileDropdown } from '@/components/profile-dropdown'
 import { Search } from '@/components/search'
-import ThemeSwitch from '@/components/theme-switch'
-import { UserNav } from '@/components/user-nav'
-import { Button } from '@/components/custom/button'
-import { apps } from './data'
+import { ThemeSwitch } from '@/components/theme-switch'
+import { apps } from './data/apps'
 
 const appText = new Map<string, string>([
   ['all', 'All Apps'],
@@ -54,7 +54,7 @@ export default function Apps() {
           <Search />
           <div className='flex items-center space-x-4'>
             <ThemeSwitch />
-            <UserNav />
+            <ProfileDropdown />
           </div>
         </div>
       </Layout.Header>

+ 28 - 0
src/features/auth/auth-layout.tsx

@@ -0,0 +1,28 @@
+interface Props {
+  children: React.ReactNode
+}
+
+export default function AuthLayout({ children }: Props) {
+  return (
+    <div className='container grid h-svh flex-col items-center justify-center bg-primary-foreground lg:max-w-none lg:px-0'>
+      <div className='mx-auto flex w-full flex-col justify-center space-y-2 sm:w-[480px] lg:p-8'>
+        <div className='mb-4 flex items-center justify-center'>
+          <svg
+            xmlns='http://www.w3.org/2000/svg'
+            viewBox='0 0 24 24'
+            fill='none'
+            stroke='currentColor'
+            strokeWidth='2'
+            strokeLinecap='round'
+            strokeLinejoin='round'
+            className='mr-2 h-6 w-6'
+          >
+            <path d='M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3' />
+          </svg>
+          <h1 className='text-xl font-medium'>Shadcn Admin</h1>
+        </div>
+        {children}
+      </div>
+    </div>
+  )
+}

+ 6 - 5
src/pages/auth/components/forgot-form.tsx → src/features/auth/forgot-password/components/forgot-password-form.tsx

@@ -1,9 +1,8 @@
 import { HTMLAttributes, useState } from 'react'
-import { cn } from '@/lib/utils'
-import { zodResolver } from '@hookform/resolvers/zod'
-import { useForm } from 'react-hook-form'
 import { z } from 'zod'
-import { Button } from '@/components/custom/button'
+import { useForm } from 'react-hook-form'
+import { zodResolver } from '@hookform/resolvers/zod'
+import { cn } from '@/lib/utils'
 import {
   Form,
   FormControl,
@@ -13,8 +12,9 @@ import {
   FormMessage,
 } from '@/components/ui/form'
 import { Input } from '@/components/ui/input'
+import { Button } from '@/components/button'
 
-interface ForgotFormProps extends HTMLAttributes<HTMLDivElement> {}
+type ForgotFormProps = HTMLAttributes<HTMLDivElement>
 
 const formSchema = z.object({
   email: z
@@ -33,6 +33,7 @@ export function ForgotForm({ className, ...props }: ForgotFormProps) {
 
   function onSubmit(data: z.infer<typeof formSchema>) {
     setIsLoading(true)
+    // eslint-disable-next-line no-console
     console.log(data)
 
     setTimeout(() => {

+ 33 - 0
src/features/auth/forgot-password/index.tsx

@@ -0,0 +1,33 @@
+import { Link } from '@tanstack/react-router'
+import { Card } from '@/components/ui/card'
+import AuthLayout from '../auth-layout'
+import { ForgotForm } from './components/forgot-password-form'
+
+export default function ForgotPassword() {
+  return (
+    <AuthLayout>
+      <Card className='p-6'>
+        <div className='mb-2 flex flex-col space-y-2 text-left'>
+          <h1 className='text-md font-semibold tracking-tight'>
+            Forgot Password
+          </h1>
+          <p className='text-sm text-muted-foreground'>
+            Enter your registered email and <br /> we will send you a link to
+            reset your password.
+          </p>
+        </div>
+        <ForgotForm />
+        <p className='mt-4 px-8 text-center text-sm text-muted-foreground'>
+          Don't have an account?{' '}
+          <Link
+            to='/sign-up'
+            className='underline underline-offset-4 hover:text-primary'
+          >
+            Sign up
+          </Link>
+          .
+        </p>
+      </Card>
+    </AuthLayout>
+  )
+}

+ 7 - 6
src/pages/auth/components/otp-form.tsx → src/features/auth/otp/components/otp-form.tsx

@@ -1,9 +1,8 @@
 import { HTMLAttributes, useState } from 'react'
-import { cn } from '@/lib/utils'
-import { zodResolver } from '@hookform/resolvers/zod'
-import { useForm } from 'react-hook-form'
 import { z } from 'zod'
-import { Button } from '@/components/custom/button'
+import { useForm } from 'react-hook-form'
+import { zodResolver } from '@hookform/resolvers/zod'
+import { cn } from '@/lib/utils'
 import {
   Form,
   FormControl,
@@ -12,10 +11,11 @@ import {
   FormMessage,
 } from '@/components/ui/form'
 import { Input } from '@/components/ui/input'
-import { PinInput, PinInputField } from '@/components/custom/pin-input'
 import { Separator } from '@/components/ui/separator'
+import { Button } from '@/components/button'
+import { PinInput, PinInputField } from '@/components/pin-input'
 
-interface OtpFormProps extends HTMLAttributes<HTMLDivElement> {}
+type OtpFormProps = HTMLAttributes<HTMLDivElement>
 
 const formSchema = z.object({
   otp: z.string().min(1, { message: 'Please enter your otp code.' }),
@@ -32,6 +32,7 @@ export function OtpForm({ className, ...props }: OtpFormProps) {
 
   function onSubmit(data: z.infer<typeof formSchema>) {
     setIsLoading(true)
+    // eslint-disable-next-line no-console
     console.log({ data })
 
     setTimeout(() => {

+ 33 - 0
src/features/auth/otp/index.tsx

@@ -0,0 +1,33 @@
+import { Link } from '@tanstack/react-router'
+import { Card } from '@/components/ui/card'
+import AuthLayout from '../auth-layout'
+import { OtpForm } from './components/otp-form'
+
+export default function Otp() {
+  return (
+    <AuthLayout>
+      <Card className='p-6'>
+        <div className='mb-2 flex flex-col space-y-2 text-left'>
+          <h1 className='text-md font-semibold tracking-tight'>
+            Two-factor Authentication
+          </h1>
+          <p className='text-sm text-muted-foreground'>
+            Please enter the authentication code. <br /> We have sent the
+            authentication code to your email.
+          </p>
+        </div>
+        <OtpForm />
+        <p className='mt-4 px-8 text-center text-sm text-muted-foreground'>
+          Haven't received it?{' '}
+          <Link
+            to='/sign-in'
+            className='underline underline-offset-4 hover:text-primary'
+          >
+            Resend a new code.
+          </Link>
+          .
+        </p>
+      </Card>
+    </AuthLayout>
+  )
+}

+ 7 - 6
src/pages/auth/components/user-auth-form.tsx → src/features/auth/sign-in/components/user-auth-form.tsx

@@ -1,9 +1,10 @@
 import { HTMLAttributes, useState } from 'react'
-import { useForm } from 'react-hook-form'
-import { Link } from 'react-router-dom'
 import { z } from 'zod'
+import { useForm } from 'react-hook-form'
 import { zodResolver } from '@hookform/resolvers/zod'
+import { Link } from '@tanstack/react-router'
 import { IconBrandFacebook, IconBrandGithub } from '@tabler/icons-react'
+import { cn } from '@/lib/utils'
 import {
   Form,
   FormControl,
@@ -13,11 +14,10 @@ import {
   FormMessage,
 } from '@/components/ui/form'
 import { Input } from '@/components/ui/input'
-import { Button } from '@/components/custom/button'
-import { PasswordInput } from '@/components/custom/password-input'
-import { cn } from '@/lib/utils'
+import { Button } from '@/components/button'
+import { PasswordInput } from '@/components/password-input'
 
-interface UserAuthFormProps extends HTMLAttributes<HTMLDivElement> {}
+type UserAuthFormProps = HTMLAttributes<HTMLDivElement>
 
 const formSchema = z.object({
   email: z
@@ -47,6 +47,7 @@ export function UserAuthForm({ className, ...props }: UserAuthFormProps) {
 
   function onSubmit(data: z.infer<typeof formSchema>) {
     setIsLoading(true)
+    // eslint-disable-next-line no-console
     console.log(data)
 
     setTimeout(() => {

+ 37 - 0
src/features/auth/sign-in/index.tsx

@@ -0,0 +1,37 @@
+import { Card } from '@/components/ui/card'
+import AuthLayout from '../auth-layout'
+import { UserAuthForm } from './components/user-auth-form'
+
+export default function SignIn() {
+  return (
+    <AuthLayout>
+      <Card className='p-6'>
+        <div className='flex flex-col space-y-2 text-left'>
+          <h1 className='text-2xl font-semibold tracking-tight'>Login</h1>
+          <p className='text-sm text-muted-foreground'>
+            Enter your email and password below <br />
+            to log into your account
+          </p>
+        </div>
+        <UserAuthForm />
+        <p className='mt-4 px-8 text-center text-sm text-muted-foreground'>
+          By clicking login, you agree to our{' '}
+          <a
+            href='/terms'
+            className='underline underline-offset-4 hover:text-primary'
+          >
+            Terms of Service
+          </a>{' '}
+          and{' '}
+          <a
+            href='/privacy'
+            className='underline underline-offset-4 hover:text-primary'
+          >
+            Privacy Policy
+          </a>
+          .
+        </p>
+      </Card>
+    </AuthLayout>
+  )
+}

+ 75 - 0
src/features/auth/sign-in/sign-in-2.tsx

@@ -0,0 +1,75 @@
+import ViteLogo from '@/assets/vite.svg'
+import { UserAuthForm } from './components/user-auth-form'
+
+export default function SignIn2() {
+  return (
+    <div className='container relative grid h-svh flex-col items-center justify-center lg:max-w-none lg:grid-cols-2 lg:px-0'>
+      <div className='relative hidden h-full flex-col bg-muted p-10 text-white dark:border-r lg:flex'>
+        <div className='absolute inset-0 bg-zinc-900' />
+        <div className='relative z-20 flex items-center text-lg font-medium'>
+          <svg
+            xmlns='http://www.w3.org/2000/svg'
+            viewBox='0 0 24 24'
+            fill='none'
+            stroke='currentColor'
+            strokeWidth='2'
+            strokeLinecap='round'
+            strokeLinejoin='round'
+            className='mr-2 h-6 w-6'
+          >
+            <path d='M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3' />
+          </svg>
+          Shadcn Admin
+        </div>
+
+        <img
+          src={ViteLogo}
+          className='relative m-auto'
+          width={301}
+          height={60}
+          alt='Vite'
+        />
+
+        <div className='relative z-20 mt-auto'>
+          <blockquote className='space-y-2'>
+            <p className='text-lg'>
+              &ldquo;This template has saved me countless hours of work and
+              helped me deliver stunning designs to my clients faster than ever
+              before.&rdquo;
+            </p>
+            <footer className='text-sm'>John Doe</footer>
+          </blockquote>
+        </div>
+      </div>
+      <div className='lg:p-8'>
+        <div className='mx-auto flex w-full flex-col justify-center space-y-2 sm:w-[350px]'>
+          <div className='flex flex-col space-y-2 text-left'>
+            <h1 className='text-2xl font-semibold tracking-tight'>Login</h1>
+            <p className='text-sm text-muted-foreground'>
+              Enter your email and password below <br />
+              to log into your account
+            </p>
+          </div>
+          <UserAuthForm />
+          <p className='px-8 text-center text-sm text-muted-foreground'>
+            By clicking login, you agree to our{' '}
+            <a
+              href='/terms'
+              className='underline underline-offset-4 hover:text-primary'
+            >
+              Terms of Service
+            </a>{' '}
+            and{' '}
+            <a
+              href='/privacy'
+              className='underline underline-offset-4 hover:text-primary'
+            >
+              Privacy Policy
+            </a>
+            .
+          </p>
+        </div>
+      </div>
+    </div>
+  )
+}

+ 6 - 5
src/pages/auth/components/sign-up-form.tsx → src/features/auth/sign-up/components/sign-up-form.tsx

@@ -1,8 +1,9 @@
 import { HTMLAttributes, useState } from 'react'
+import { z } from 'zod'
 import { useForm } from 'react-hook-form'
 import { zodResolver } from '@hookform/resolvers/zod'
 import { IconBrandFacebook, IconBrandGithub } from '@tabler/icons-react'
-import { z } from 'zod'
+import { cn } from '@/lib/utils'
 import {
   Form,
   FormControl,
@@ -12,11 +13,10 @@ import {
   FormMessage,
 } from '@/components/ui/form'
 import { Input } from '@/components/ui/input'
-import { Button } from '@/components/custom/button'
-import { PasswordInput } from '@/components/custom/password-input'
-import { cn } from '@/lib/utils'
+import { Button } from '@/components/button'
+import { PasswordInput } from '@/components/password-input'
 
-interface SignUpFormProps extends HTMLAttributes<HTMLDivElement> {}
+type SignUpFormProps = HTMLAttributes<HTMLDivElement>
 
 const formSchema = z
   .object({
@@ -53,6 +53,7 @@ export function SignUpForm({ className, ...props }: SignUpFormProps) {
 
   function onSubmit(data: z.infer<typeof formSchema>) {
     setIsLoading(true)
+    // eslint-disable-next-line no-console
     console.log(data)
 
     setTimeout(() => {

+ 46 - 0
src/features/auth/sign-up/index.tsx

@@ -0,0 +1,46 @@
+import { Link } from '@tanstack/react-router'
+import { Card } from '@/components/ui/card'
+import AuthLayout from '../auth-layout'
+import { SignUpForm } from './components/sign-up-form'
+
+export default function SignUp() {
+  return (
+    <AuthLayout>
+      <Card className='p-6'>
+        <div className='mb-2 flex flex-col space-y-2 text-left'>
+          <h1 className='text-lg font-semibold tracking-tight'>
+            Create an account
+          </h1>
+          <p className='text-sm text-muted-foreground'>
+            Enter your email and password to create an account. <br />
+            Already have an account?{' '}
+            <Link
+              to='/sign-in'
+              className='underline underline-offset-4 hover:text-primary'
+            >
+              Sign In
+            </Link>
+          </p>
+        </div>
+        <SignUpForm />
+        <p className='mt-4 px-8 text-center text-sm text-muted-foreground'>
+          By creating an account, you agree to our{' '}
+          <a
+            href='/terms'
+            className='underline underline-offset-4 hover:text-primary'
+          >
+            Terms of Service
+          </a>{' '}
+          and{' '}
+          <a
+            href='/privacy'
+            className='underline underline-offset-4 hover:text-primary'
+          >
+            Privacy Policy
+          </a>
+          .
+        </p>
+      </Card>
+    </AuthLayout>
+  )
+}

+ 0 - 0
src/data/conversations.json → src/features/chats/data/convo.json


+ 10 - 11
src/pages/chats/index.tsx → src/features/chats/index.tsx

@@ -1,6 +1,6 @@
 import { useState } from 'react'
 import { Fragment } from 'react/jsx-runtime'
-import dayjs from 'dayjs'
+import { format } from 'date-fns'
 import {
   IconArrowLeft,
   IconDotsVertical,
@@ -15,16 +15,15 @@ import {
   IconVideo,
 } from '@tabler/icons-react'
 import { cn } from '@/lib/utils'
-import { Separator } from '@/components/ui/separator'
 import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
-import { Layout } from '@/components/custom/layout'
+import { Separator } from '@/components/ui/separator'
+import { Button } from '@/components/button'
+import { Layout } from '@/components/layout'
+import { ProfileDropdown } from '@/components/profile-dropdown'
 import { Search } from '@/components/search'
-import ThemeSwitch from '@/components/theme-switch'
-import { UserNav } from '@/components/user-nav'
-import { Button } from '@/components/custom/button'
-
+import { ThemeSwitch } from '@/components/theme-switch'
 // Fake Data
-import { conversations } from '@/data/conversations.json'
+import { conversations } from './data/convo.json'
 
 type ChatUser = (typeof conversations)[number]
 type Convo = ChatUser['messages'][number]
@@ -43,7 +42,7 @@ export default function Chats() {
 
   const currentMessage = selectedUser.messages.reduce(
     (acc: Record<string, Convo[]>, obj) => {
-      const key = dayjs(obj.timestamp).format('D MMM, YYYY')
+      const key = format(obj.timestamp, 'd MMM, yyyy')
 
       // Create an array for the category if it doesn't exist
       if (!acc[key]) {
@@ -65,7 +64,7 @@ export default function Chats() {
         <Search />
         <div className='ml-auto flex items-center space-x-4'>
           <ThemeSwitch />
-          <UserNav />
+          <ProfileDropdown />
         </div>
       </Layout.Header>
 
@@ -230,7 +229,7 @@ export default function Chats() {
                                   msg.sender === 'You' && 'text-right'
                                 )}
                               >
-                                {dayjs(msg.timestamp).format('h:mm a')}
+                                {format(msg.timestamp, 'h:mm a')}
                               </span>
                             </div>
                           ))}

+ 0 - 0
src/pages/dashboard/components/overview.tsx → src/features/dashboard/components/overview.tsx


+ 0 - 0
src/pages/dashboard/components/recent-sales.tsx → src/features/dashboard/components/recent-sales.tsx


+ 38 - 38
src/pages/dashboard/index.tsx → src/features/dashboard/index.tsx

@@ -1,5 +1,3 @@
-import { Layout } from '@/components/custom/layout'
-import { Button } from '@/components/custom/button'
 import {
   Card,
   CardContent,
@@ -7,18 +5,17 @@ import {
   CardHeader,
   CardTitle,
 } from '@/components/ui/card'
-import { Search } from '@/components/search'
 import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
-import ThemeSwitch from '@/components/theme-switch'
-import { TopNav } from '@/components/top-nav'
-import { UserNav } from '@/components/user-nav'
-import { RecentSales } from './components/recent-sales'
+import { Button } from '@/components/button'
+import { Layout } from '@/components/layout'
+import { TopNav } from '@/components/layout/top-nav'
+import { ProfileDropdown } from '@/components/profile-dropdown'
+import { Search } from '@/components/search'
+import { ThemeSwitch } from '@/components/theme-switch'
 import { Overview } from './components/overview'
-import { useTranslations } from 'use-intl'
-import LanguageSwitch from '@/components/language-switch'
+import { RecentSales } from './components/recent-sales'
 
 export default function Dashboard() {
-  const t = useTranslations('dashboard')
   return (
     <Layout>
       {/* ===== Top Heading ===== */}
@@ -27,19 +24,16 @@ export default function Dashboard() {
         <div className='ml-auto flex items-center space-x-4'>
           <Search />
           <ThemeSwitch />
-          <LanguageSwitch />
-          <UserNav />
+          <ProfileDropdown />
         </div>
       </Layout.Header>
 
       {/* ===== Main ===== */}
       <Layout.Body>
         <div className='mb-2 flex items-center justify-between space-y-2'>
-          <h1 className='text-2xl font-bold tracking-tight'>
-            {t('dashboard')}
-          </h1>
+          <h1 className='text-2xl font-bold tracking-tight'>Dashboard</h1>
           <div className='flex items-center space-x-2'>
-            <Button>{t('download')}</Button>
+            <Button>Download</Button>
           </div>
         </div>
         <Tabs
@@ -49,11 +43,15 @@ export default function Dashboard() {
         >
           <div className='w-full overflow-x-auto pb-2'>
             <TabsList>
-              <TabsTrigger value='overview'>{t('overview')}</TabsTrigger>
-              <TabsTrigger value='analytics'>{t('analytics')}</TabsTrigger>
-              <TabsTrigger value='reports'>{t('reports')}</TabsTrigger>
-              <TabsTrigger value='notifications'>
-                {t('notifications')}
+              <TabsTrigger value='overview'>Overview</TabsTrigger>
+              <TabsTrigger value='analytics' disabled>
+                Analytics
+              </TabsTrigger>
+              <TabsTrigger value='reports' disabled>
+                Reports
+              </TabsTrigger>
+              <TabsTrigger value='notifications' disabled>
+                Notifications
               </TabsTrigger>
             </TabsList>
           </div>
@@ -62,7 +60,7 @@ export default function Dashboard() {
               <Card>
                 <CardHeader className='flex flex-row items-center justify-between space-y-0 pb-2'>
                   <CardTitle className='text-sm font-medium'>
-                    {t('total_revenue')}
+                    Total Revenue
                   </CardTitle>
                   <svg
                     xmlns='http://www.w3.org/2000/svg'
@@ -80,14 +78,14 @@ export default function Dashboard() {
                 <CardContent>
                   <div className='text-2xl font-bold'>$45,231.89</div>
                   <p className='text-xs text-muted-foreground'>
-                    {t('from_last_month', { amount: '+20.1%' })}
+                    +20.1% from last month
                   </p>
                 </CardContent>
               </Card>
               <Card>
                 <CardHeader className='flex flex-row items-center justify-between space-y-0 pb-2'>
                   <CardTitle className='text-sm font-medium'>
-                    {t('subscriptions')}
+                    Subscriptions
                   </CardTitle>
                   <svg
                     xmlns='http://www.w3.org/2000/svg'
@@ -107,15 +105,13 @@ export default function Dashboard() {
                 <CardContent>
                   <div className='text-2xl font-bold'>+2350</div>
                   <p className='text-xs text-muted-foreground'>
-                    {t('from_last_month', { amount: '+180.1%' })}
+                    +180.1% from last month
                   </p>
                 </CardContent>
               </Card>
               <Card>
                 <CardHeader className='flex flex-row items-center justify-between space-y-0 pb-2'>
-                  <CardTitle className='text-sm font-medium'>
-                    {t('sales')}
-                  </CardTitle>
+                  <CardTitle className='text-sm font-medium'>Sales</CardTitle>
                   <svg
                     xmlns='http://www.w3.org/2000/svg'
                     viewBox='0 0 24 24'
@@ -133,14 +129,14 @@ export default function Dashboard() {
                 <CardContent>
                   <div className='text-2xl font-bold'>+12,234</div>
                   <p className='text-xs text-muted-foreground'>
-                    {t('from_last_month', { amount: '+19%' })}
+                    +19% from last month
                   </p>
                 </CardContent>
               </Card>
               <Card>
                 <CardHeader className='flex flex-row items-center justify-between space-y-0 pb-2'>
                   <CardTitle className='text-sm font-medium'>
-                    {t('active_now')}
+                    Active Now
                   </CardTitle>
                   <svg
                     xmlns='http://www.w3.org/2000/svg'
@@ -158,7 +154,7 @@ export default function Dashboard() {
                 <CardContent>
                   <div className='text-2xl font-bold'>+573</div>
                   <p className='text-xs text-muted-foreground'>
-                    {t('since_last_hour', { amount: '+201' })}
+                    +201 since last hour
                   </p>
                 </CardContent>
               </Card>
@@ -166,7 +162,7 @@ export default function Dashboard() {
             <div className='grid grid-cols-1 gap-4 lg:grid-cols-7'>
               <Card className='col-span-1 lg:col-span-4'>
                 <CardHeader>
-                  <CardTitle>{t('overview')}</CardTitle>
+                  <CardTitle>Overview</CardTitle>
                 </CardHeader>
                 <CardContent className='pl-2'>
                   <Overview />
@@ -174,9 +170,9 @@ export default function Dashboard() {
               </Card>
               <Card className='col-span-1 lg:col-span-3'>
                 <CardHeader>
-                  <CardTitle>{t('recent_sales')}</CardTitle>
+                  <CardTitle>Recent Sales</CardTitle>
                   <CardDescription>
-                    {t('recent_sales_desc', { amount: '265' })}
+                    You made 265 sales this month.
                   </CardDescription>
                 </CardHeader>
                 <CardContent>
@@ -193,23 +189,27 @@ export default function Dashboard() {
 
 const topNav = [
   {
-    title: 'dashboard.overview',
+    title: 'Overview',
     href: 'dashboard/overview',
     isActive: true,
+    disabled: false,
   },
   {
-    title: 'dashboard.customers',
+    title: 'Customers',
     href: 'dashboard/customers',
     isActive: false,
+    disabled: true,
   },
   {
-    title: 'dashboard.products',
+    title: 'Products',
     href: 'dashboard/products',
     isActive: false,
+    disabled: true,
   },
   {
-    title: 'dashboard.settings',
+    title: 'Settings',
     href: 'dashboard/settings',
     isActive: false,
+    disabled: true,
   },
 ]

+ 25 - 0
src/features/errors/forbidden.tsx

@@ -0,0 +1,25 @@
+import { useNavigate, useRouter } from '@tanstack/react-router'
+import { Button } from '@/components/button'
+
+export default function ForbiddenError() {
+  const navigate = useNavigate()
+  const { history } = useRouter()
+  return (
+    <div className='h-svh'>
+      <div className='m-auto flex h-full w-full flex-col items-center justify-center gap-2'>
+        <h1 className='text-[7rem] font-bold leading-tight'>403</h1>
+        <span className='font-medium'>Access Forbidden</span>
+        <p className='text-center text-muted-foreground'>
+          You don't have necessary permission <br />
+          to view this resource.
+        </p>
+        <div className='mt-6 flex gap-4'>
+          <Button variant='outline' onClick={() => history.go(-1)}>
+            Go Back
+          </Button>
+          <Button onClick={() => navigate({ to: '/' })}>Back to Home</Button>
+        </div>
+      </div>
+    </div>
+  )
+}

+ 8 - 9
src/pages/errors/general-error.tsx → src/features/errors/general-error.tsx

@@ -1,7 +1,6 @@
-import { useNavigate } from 'react-router-dom'
-import { Button } from '@/components/custom/button'
+import { useNavigate, useRouter } from '@tanstack/react-router'
 import { cn } from '@/lib/utils'
-import { useTranslations } from 'use-intl'
+import { Button } from '@/components/button'
 
 interface GeneralErrorProps extends React.HTMLAttributes<HTMLDivElement> {
   minimal?: boolean
@@ -12,23 +11,23 @@ export default function GeneralError({
   minimal = false,
 }: GeneralErrorProps) {
   const navigate = useNavigate()
-  const t = useTranslations('error')
+  const { history } = useRouter()
   return (
     <div className={cn('h-svh w-full', className)}>
       <div className='m-auto flex h-full w-full flex-col items-center justify-center gap-2'>
         {!minimal && (
           <h1 className='text-[7rem] font-bold leading-tight'>500</h1>
         )}
-        <span className='font-medium'>{t('500_title')}</span>
+        <span className='font-medium'>Oops! Something went wrong {`:')`}</span>
         <p className='text-center text-muted-foreground'>
-          {t.rich('500_desc', { br: () => <br /> })}
+          We apologize for the inconvenience. <br /> Please try again later.
         </p>
         {!minimal && (
           <div className='mt-6 flex gap-4'>
-            <Button variant='outline' onClick={() => navigate(-1)}>
-              {t('go_back')}
+            <Button variant='outline' onClick={() => history.go(-1)}>
+              Go Back
             </Button>
-            <Button onClick={() => navigate('/')}>{t('back_to_home')}</Button>
+            <Button onClick={() => navigate({ to: '/' })}>Back to Home</Button>
           </div>
         )}
       </div>

+ 5 - 6
src/pages/errors/maintenance-error.tsx → src/features/errors/maintenance-error.tsx

@@ -1,18 +1,17 @@
-import { Button } from '@/components/custom/button'
-import { useTranslations } from 'use-intl'
+import { Button } from '@/components/button'
 
 export default function MaintenanceError() {
-  const t = useTranslations('error')
   return (
     <div className='h-svh'>
       <div className='m-auto flex h-full w-full flex-col items-center justify-center gap-2'>
         <h1 className='text-[7rem] font-bold leading-tight'>503</h1>
-        <span className='font-medium'>{t('maintenance_title')}</span>
+        <span className='font-medium'>Website is under maintenance!</span>
         <p className='text-center text-muted-foreground'>
-          {t.rich('maintenance_desc', { br: () => <br /> })}
+          The site is not available at the moment. <br />
+          We'll be back online shortly.
         </p>
         <div className='mt-6 flex gap-4'>
-          <Button variant='outline'>{t('learn_more')}</Button>
+          <Button variant='outline'>Learn more</Button>
         </div>
       </div>
     </div>

+ 25 - 0
src/features/errors/not-found-error.tsx

@@ -0,0 +1,25 @@
+import { useNavigate, useRouter } from '@tanstack/react-router'
+import { Button } from '@/components/button'
+
+export default function NotFoundError() {
+  const navigate = useNavigate()
+  const { history } = useRouter()
+  return (
+    <div className='h-svh'>
+      <div className='m-auto flex h-full w-full flex-col items-center justify-center gap-2'>
+        <h1 className='text-[7rem] font-bold leading-tight'>404</h1>
+        <span className='font-medium'>Oops! Page Not Found!</span>
+        <p className='text-center text-muted-foreground'>
+          It seems like the page you're looking for <br />
+          does not exist or might have been removed.
+        </p>
+        <div className='mt-6 flex gap-4'>
+          <Button variant='outline' onClick={() => history.go(-1)}>
+            Go Back
+          </Button>
+          <Button onClick={() => navigate({ to: '/' })}>Back to Home</Button>
+        </div>
+      </div>
+    </div>
+  )
+}

+ 25 - 0
src/features/errors/unauthorized-error.tsx

@@ -0,0 +1,25 @@
+import { useNavigate, useRouter } from '@tanstack/react-router'
+import { Button } from '@/components/button'
+
+export default function UnauthorisedError() {
+  const navigate = useNavigate()
+  const { history } = useRouter()
+  return (
+    <div className='h-svh'>
+      <div className='m-auto flex h-full w-full flex-col items-center justify-center gap-2'>
+        <h1 className='text-[7rem] font-bold leading-tight'>401</h1>
+        <span className='font-medium'>Unauthorized Access</span>
+        <p className='text-center text-muted-foreground'>
+          Please log in with the appropriate credentials <br /> to access this
+          resource.
+        </p>
+        <div className='mt-6 flex gap-4'>
+          <Button variant='outline' onClick={() => history.go(-1)}>
+            Go Back
+          </Button>
+          <Button onClick={() => navigate({ to: '/' })}>Back to Home</Button>
+        </div>
+      </div>
+    </div>
+  )
+}

+ 7 - 7
src/pages/settings/account/account-form.tsx → src/features/settings/account/account-form.tsx

@@ -1,10 +1,10 @@
-import { zodResolver } from '@hookform/resolvers/zod'
-import { CalendarIcon, CaretSortIcon, CheckIcon } from '@radix-ui/react-icons'
-import { useForm } from 'react-hook-form'
 import { z } from 'zod'
-import dayjs from 'dayjs'
+import { format } from 'date-fns'
+import { useForm } from 'react-hook-form'
+import { CalendarIcon, CaretSortIcon, CheckIcon } from '@radix-ui/react-icons'
+import { zodResolver } from '@hookform/resolvers/zod'
 import { cn } from '@/lib/utils'
-import { Button } from '@/components/custom/button'
+import { toast } from '@/hooks/use-toast'
 import { Calendar } from '@/components/ui/calendar'
 import {
   Command,
@@ -28,7 +28,7 @@ import {
   PopoverContent,
   PopoverTrigger,
 } from '@/components/ui/popover'
-import { toast } from '@/components/ui/use-toast'
+import { Button } from '@/components/button'
 
 const languages = [
   { label: 'English', value: 'en' },
@@ -121,7 +121,7 @@ export function AccountForm() {
                       )}
                     >
                       {field.value ? (
-                        dayjs(field.value).format('MMM D, YYYY')
+                        format(field.value, 'MMM d, yyyy')
                       ) : (
                         <span>Pick a date</span>
                       )}

+ 1 - 1
src/pages/settings/account/index.tsx → src/features/settings/account/index.tsx

@@ -1,5 +1,5 @@
-import { AccountForm } from './account-form'
 import ContentSection from '../components/content-section'
+import { AccountForm } from './account-form'
 
 export default function SettingsAccount() {
   return (

+ 5 - 6
src/pages/settings/appearance/appearance-form.tsx → src/features/settings/appearance/appearance-form.tsx

@@ -1,10 +1,9 @@
-import { zodResolver } from '@hookform/resolvers/zod'
-import { ChevronDownIcon } from '@radix-ui/react-icons'
-import { useForm } from 'react-hook-form'
 import { z } from 'zod'
-
+import { useForm } from 'react-hook-form'
+import { ChevronDownIcon } from '@radix-ui/react-icons'
+import { zodResolver } from '@hookform/resolvers/zod'
 import { cn } from '@/lib/utils'
-import { Button, buttonVariants } from '@/components/custom/button'
+import { toast } from '@/hooks/use-toast'
 import {
   Form,
   FormControl,
@@ -15,7 +14,7 @@ import {
   FormMessage,
 } from '@/components/ui/form'
 import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
-import { toast } from '@/components/ui/use-toast'
+import { Button, buttonVariants } from '@/components/button'
 
 const appearanceFormSchema = z.object({
   theme: z.enum(['light', 'dark'], {

+ 1 - 1
src/pages/settings/appearance/index.tsx → src/features/settings/appearance/index.tsx

@@ -1,5 +1,5 @@
-import { AppearanceForm } from './appearance-form'
 import ContentSection from '../components/content-section'
+import { AppearanceForm } from './appearance-form'
 
 export default function SettingsAppearance() {
   return (

+ 0 - 0
src/pages/settings/components/content-section.tsx → src/features/settings/components/content-section.tsx


+ 5 - 4
src/pages/settings/components/sidebar-nav.tsx → src/features/settings/components/sidebar-nav.tsx

@@ -1,6 +1,7 @@
 import { useState } from 'react'
-import { Link, useLocation, useNavigate } from 'react-router-dom'
-import { buttonVariants } from '@/components/custom/button'
+import { useLocation, useNavigate } from '@tanstack/react-router'
+import { Link } from '@tanstack/react-router'
+import { cn } from '@/lib/utils'
 import {
   Select,
   SelectContent,
@@ -8,7 +9,7 @@ import {
   SelectTrigger,
   SelectValue,
 } from '@/components/ui/select'
-import { cn } from '@/lib/utils'
+import { buttonVariants } from '@/components/button'
 
 interface SidebarNavProps extends React.HTMLAttributes<HTMLElement> {
   items: {
@@ -29,7 +30,7 @@ export default function SidebarNav({
 
   const handleSelect = (e: string) => {
     setVal(e)
-    navigate(e)
+    navigate({ to: e })
   }
 
   return (

+ 4 - 5
src/pages/settings/display/display-form.tsx → src/features/settings/display/display-form.tsx

@@ -1,8 +1,7 @@
-import { zodResolver } from '@hookform/resolvers/zod'
-import { useForm } from 'react-hook-form'
 import { z } from 'zod'
-
-import { Button } from '@/components/custom/button'
+import { useForm } from 'react-hook-form'
+import { zodResolver } from '@hookform/resolvers/zod'
+import { toast } from '@/hooks/use-toast'
 import { Checkbox } from '@/components/ui/checkbox'
 import {
   Form,
@@ -13,7 +12,7 @@ import {
   FormLabel,
   FormMessage,
 } from '@/components/ui/form'
-import { toast } from '@/components/ui/use-toast'
+import { Button } from '@/components/button'
 
 const items = [
   {

+ 1 - 1
src/pages/settings/display/index.tsx → src/features/settings/display/index.tsx

@@ -1,5 +1,5 @@
-import { DisplayForm } from './display-form'
 import ContentSection from '../components/content-section'
+import { DisplayForm } from './display-form'
 
 export default function SettingsDisplay() {
   return (

+ 6 - 12
src/pages/settings/index.tsx → src/features/settings/index.tsx

@@ -1,17 +1,16 @@
-import { Outlet } from 'react-router-dom'
+import { Outlet } from '@tanstack/react-router'
 import {
   IconBrowserCheck,
-  IconExclamationCircle,
   IconNotification,
   IconPalette,
   IconTool,
   IconUser,
 } from '@tabler/icons-react'
-import { Layout } from '@/components/custom/layout'
-import { Search } from '@/components/search'
 import { Separator } from '@/components/ui/separator'
-import ThemeSwitch from '@/components/theme-switch'
-import { UserNav } from '@/components/user-nav'
+import { Layout } from '@/components/layout'
+import { ProfileDropdown } from '@/components/profile-dropdown'
+import { Search } from '@/components/search'
+import { ThemeSwitch } from '@/components/theme-switch'
 import SidebarNav from './components/sidebar-nav'
 
 export default function Settings() {
@@ -22,7 +21,7 @@ export default function Settings() {
         <Search />
         <div className='ml-auto flex items-center space-x-4'>
           <ThemeSwitch />
-          <UserNav />
+          <ProfileDropdown />
         </div>
       </Layout.Header>
 
@@ -75,9 +74,4 @@ const sidebarNavItems = [
     icon: <IconBrowserCheck size={18} />,
     href: '/settings/display',
   },
-  {
-    title: 'Error Example',
-    icon: <IconExclamationCircle size={18} />,
-    href: '/settings/error-example',
-  },
 ]

+ 1 - 1
src/pages/settings/notifications/index.tsx → src/features/settings/notifications/index.tsx

@@ -1,5 +1,5 @@
-import { NotificationsForm } from './notifications-form'
 import ContentSection from '../components/content-section'
+import { NotificationsForm } from './notifications-form'
 
 export default function SettingsNotifications() {
   return (

+ 8 - 9
src/pages/settings/notifications/notifications-form.tsx → src/features/settings/notifications/notifications-form.tsx

@@ -1,8 +1,8 @@
-import { zodResolver } from '@hookform/resolvers/zod'
-import { useForm } from 'react-hook-form'
 import { z } from 'zod'
-
-import { Button } from '@/components/custom/button'
+import { useForm } from 'react-hook-form'
+import { zodResolver } from '@hookform/resolvers/zod'
+import { Link } from '@tanstack/react-router'
+import { toast } from '@/hooks/use-toast'
 import { Checkbox } from '@/components/ui/checkbox'
 import {
   Form,
@@ -15,8 +15,7 @@ import {
 } from '@/components/ui/form'
 import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
 import { Switch } from '@/components/ui/switch'
-import { toast } from '@/components/ui/use-toast'
-import { Link } from 'react-router-dom'
+import { Button } from '@/components/button'
 
 const notificationsFormSchema = z.object({
   type: z.enum(['all', 'mentions', 'none'], {
@@ -63,7 +62,7 @@ export function NotificationsForm() {
           control={form.control}
           name='type'
           render={({ field }) => (
-            <FormItem className='space-y-3'>
+            <FormItem className='space-y-3 relative'>
               <FormLabel>Notify me about...</FormLabel>
               <FormControl>
                 <RadioGroup
@@ -99,7 +98,7 @@ export function NotificationsForm() {
             </FormItem>
           )}
         />
-        <div>
+        <div className='relative'>
           <h3 className='mb-4 text-lg font-medium'>Email Notifications</h3>
           <div className='space-y-4'>
             <FormField
@@ -194,7 +193,7 @@ export function NotificationsForm() {
           control={form.control}
           name='mobile'
           render={({ field }) => (
-            <FormItem className='flex flex-row items-start space-x-3 space-y-0'>
+            <FormItem className='flex flex-row items-start space-x-3 space-y-0 relative'>
               <FormControl>
                 <Checkbox
                   checked={field.value}

+ 1 - 1
src/pages/settings/profile/index.tsx → src/features/settings/profile/index.tsx

@@ -1,5 +1,5 @@
-import ProfileForm from './profile-form'
 import ContentSection from '../components/content-section'
+import ProfileForm from './profile-form'
 
 export default function SettingsProfile() {
   return (

+ 6 - 6
src/pages/settings/profile/profile-form.tsx → src/features/settings/profile/profile-form.tsx

@@ -1,7 +1,9 @@
 import { z } from 'zod'
-import { Link } from 'react-router-dom'
 import { useFieldArray, useForm } from 'react-hook-form'
-import { Button } from '@/components/custom/button'
+import { zodResolver } from '@hookform/resolvers/zod'
+import { Link } from '@tanstack/react-router'
+import { cn } from '@/lib/utils'
+import { toast } from '@/hooks/use-toast'
 import {
   Form,
   FormControl,
@@ -20,9 +22,7 @@ import {
   SelectValue,
 } from '@/components/ui/select'
 import { Textarea } from '@/components/ui/textarea'
-import { toast } from '@/components/ui/use-toast'
-import { cn } from '@/lib/utils'
-import { zodResolver } from '@hookform/resolvers/zod'
+import { Button } from '@/components/button'
 
 const profileFormSchema = z.object({
   username: z
@@ -122,7 +122,7 @@ export default function ProfileForm() {
               </Select>
               <FormDescription>
                 You can manage verified email addresses in your{' '}
-                <Link to='/examples/forms'>email settings</Link>.
+                <Link to='/'>email settings</Link>.
               </FormDescription>
               <FormMessage />
             </FormItem>

+ 2 - 4
src/pages/tasks/components/columns.tsx → src/features/tasks/components/columns.tsx

@@ -1,12 +1,10 @@
 import { ColumnDef } from '@tanstack/react-table'
-
 import { Badge } from '@/components/ui/badge'
 import { Checkbox } from '@/components/ui/checkbox'
-import { DataTableColumnHeader } from './data-table-column-header'
-import { DataTableRowActions } from './data-table-row-actions'
-
 import { labels, priorities, statuses } from '../data/data'
 import { Task } from '../data/schema'
+import { DataTableColumnHeader } from './data-table-column-header'
+import { DataTableRowActions } from './data-table-row-actions'
 
 export const columns: ColumnDef<Task>[] = [
   {

+ 2 - 3
src/pages/tasks/components/data-table-column-header.tsx → src/features/tasks/components/data-table-column-header.tsx

@@ -5,8 +5,7 @@ import {
   EyeNoneIcon,
 } from '@radix-ui/react-icons'
 import { Column } from '@tanstack/react-table'
-
-import { Button } from '@/components/custom/button'
+import { cn } from '@/lib/utils'
 import {
   DropdownMenu,
   DropdownMenuContent,
@@ -14,7 +13,7 @@ import {
   DropdownMenuSeparator,
   DropdownMenuTrigger,
 } from '@/components/ui/dropdown-menu'
-import { cn } from '@/lib/utils'
+import { Button } from '@/components/button'
 
 interface DataTableColumnHeaderProps<TData, TValue>
   extends React.HTMLAttributes<HTMLDivElement> {

+ 1 - 2
src/pages/tasks/components/data-table-faceted-filter.tsx → src/features/tasks/components/data-table-faceted-filter.tsx

@@ -1,10 +1,8 @@
 import * as React from 'react'
 import { CheckIcon, PlusCircledIcon } from '@radix-ui/react-icons'
 import { Column } from '@tanstack/react-table'
-
 import { cn } from '@/lib/utils'
 import { Badge } from '@/components/ui/badge'
-import { Button } from '@/components/custom/button'
 import {
   Command,
   CommandEmpty,
@@ -20,6 +18,7 @@ import {
   PopoverTrigger,
 } from '@/components/ui/popover'
 import { Separator } from '@/components/ui/separator'
+import { Button } from '@/components/button'
 
 interface DataTableFacetedFilterProps<TData, TValue> {
   column?: Column<TData, TValue>

+ 1 - 2
src/pages/tasks/components/data-table-pagination.tsx → src/features/tasks/components/data-table-pagination.tsx

@@ -5,8 +5,6 @@ import {
   DoubleArrowRightIcon,
 } from '@radix-ui/react-icons'
 import { Table } from '@tanstack/react-table'
-
-import { Button } from '@/components/custom/button'
 import {
   Select,
   SelectContent,
@@ -14,6 +12,7 @@ import {
   SelectTrigger,
   SelectValue,
 } from '@/components/ui/select'
+import { Button } from '@/components/button'
 
 interface DataTablePaginationProps<TData> {
   table: Table<TData>

+ 1 - 3
src/pages/tasks/components/data-table-row-actions.tsx → src/features/tasks/components/data-table-row-actions.tsx

@@ -1,7 +1,5 @@
 import { DotsHorizontalIcon } from '@radix-ui/react-icons'
 import { Row } from '@tanstack/react-table'
-
-import { Button } from '@/components/custom/button'
 import {
   DropdownMenu,
   DropdownMenuContent,
@@ -15,7 +13,7 @@ import {
   DropdownMenuSubTrigger,
   DropdownMenuTrigger,
 } from '@/components/ui/dropdown-menu'
-
+import { Button } from '@/components/button'
 import { labels } from '../data/data'
 import { taskSchema } from '../data/schema'
 

+ 1 - 3
src/pages/tasks/components/data-table-toolbar.tsx → src/features/tasks/components/data-table-toolbar.tsx

@@ -1,10 +1,8 @@
 import { Cross2Icon } from '@radix-ui/react-icons'
 import { Table } from '@tanstack/react-table'
-
-import { Button } from '@/components/custom/button'
 import { Input } from '@/components/ui/input'
+import { Button } from '@/components/button'
 import { DataTableViewOptions } from '../components/data-table-view-options'
-
 import { priorities, statuses } from '../data/data'
 import { DataTableFacetedFilter } from './data-table-faceted-filter'
 

+ 1 - 2
src/pages/tasks/components/data-table-view-options.tsx → src/features/tasks/components/data-table-view-options.tsx

@@ -1,8 +1,6 @@
 import { DropdownMenuTrigger } from '@radix-ui/react-dropdown-menu'
 import { MixerHorizontalIcon } from '@radix-ui/react-icons'
 import { Table } from '@tanstack/react-table'
-
-import { Button } from '@/components/custom/button'
 import {
   DropdownMenu,
   DropdownMenuCheckboxItem,
@@ -10,6 +8,7 @@ import {
   DropdownMenuLabel,
   DropdownMenuSeparator,
 } from '@/components/ui/dropdown-menu'
+import { Button } from '@/components/button'
 
 interface DataTableViewOptionsProps<TData> {
   table: Table<TData>

+ 0 - 2
src/pages/tasks/components/data-table.tsx → src/features/tasks/components/data-table.tsx

@@ -13,7 +13,6 @@ import {
   getSortedRowModel,
   useReactTable,
 } from '@tanstack/react-table'
-
 import {
   Table,
   TableBody,
@@ -22,7 +21,6 @@ import {
   TableHeader,
   TableRow,
 } from '@/components/ui/table'
-
 import { DataTablePagination } from '../components/data-table-pagination'
 import { DataTableToolbar } from '../components/data-table-toolbar'
 

+ 17 - 17
src/pages/tasks/data/data.tsx → src/features/tasks/data/data.tsx

@@ -1,13 +1,13 @@
 import {
-  ArrowDownIcon,
-  ArrowRightIcon,
-  ArrowUpIcon,
-  CheckCircledIcon,
-  CircleIcon,
-  CrossCircledIcon,
-  QuestionMarkCircledIcon,
-  StopwatchIcon,
-} from '@radix-ui/react-icons'
+  IconArrowDown,
+  IconArrowRight,
+  IconArrowUp,
+  IconCircle,
+  IconCircleCheck,
+  IconCircleX,
+  IconExclamationCircle,
+  IconStopwatch,
+} from '@tabler/icons-react'
 
 export const labels = [
   {
@@ -28,27 +28,27 @@ export const statuses = [
   {
     value: 'backlog',
     label: 'Backlog',
-    icon: QuestionMarkCircledIcon,
+    icon: IconExclamationCircle,
   },
   {
     value: 'todo',
     label: 'Todo',
-    icon: CircleIcon,
+    icon: IconCircle,
   },
   {
     value: 'in progress',
     label: 'In Progress',
-    icon: StopwatchIcon,
+    icon: IconStopwatch,
   },
   {
     value: 'done',
     label: 'Done',
-    icon: CheckCircledIcon,
+    icon: IconCircleCheck,
   },
   {
     value: 'canceled',
     label: 'Canceled',
-    icon: CrossCircledIcon,
+    icon: IconCircleX,
   },
 ]
 
@@ -56,16 +56,16 @@ export const priorities = [
   {
     label: 'Low',
     value: 'low',
-    icon: ArrowDownIcon,
+    icon: IconArrowDown,
   },
   {
     label: 'Medium',
     value: 'medium',
-    icon: ArrowRightIcon,
+    icon: IconArrowRight,
   },
   {
     label: 'High',
     value: 'high',
-    icon: ArrowUpIcon,
+    icon: IconArrowUp,
   },
 ]

+ 0 - 0
src/pages/tasks/data/schema.ts → src/features/tasks/data/schema.ts


+ 0 - 0
src/pages/tasks/data/tasks.ts → src/features/tasks/data/tasks.ts


+ 6 - 6
src/pages/tasks/index.tsx → src/features/tasks/index.tsx

@@ -1,9 +1,9 @@
-import { Layout } from '@/components/custom/layout'
+import { Layout } from '@/components/layout'
+import { ProfileDropdown } from '@/components/profile-dropdown'
 import { Search } from '@/components/search'
-import ThemeSwitch from '@/components/theme-switch'
-import { UserNav } from '@/components/user-nav'
-import { DataTable } from './components/data-table'
+import { ThemeSwitch } from '@/components/theme-switch'
 import { columns } from './components/columns'
+import { DataTable } from './components/data-table'
 import { tasks } from './data/tasks'
 
 export default function Tasks() {
@@ -14,14 +14,14 @@ export default function Tasks() {
         <Search />
         <div className='ml-auto flex items-center space-x-4'>
           <ThemeSwitch />
-          <UserNav />
+          <ProfileDropdown />
         </div>
       </Layout.Header>
 
       <Layout.Body>
         <div className='mb-2 flex items-center justify-between space-y-2'>
           <div>
-            <h2 className='text-2xl font-bold tracking-tight'>Welcome back!</h2>
+            <h2 className='text-2xl font-bold tracking-tight'>Tasks</h2>
             <p className='text-muted-foreground'>
               Here&apos;s a list of your tasks for this month!
             </p>

+ 0 - 15
src/hooks/use-check-active-nav.tsx

@@ -1,15 +0,0 @@
-import { useLocation } from 'react-router-dom'
-
-export default function useCheckActiveNav() {
-  const { pathname } = useLocation()
-
-  const checkActiveNav = (nav: string) => {
-    const pathArray = pathname.split('/').filter((item) => item !== '')
-
-    if (nav === '/' && pathArray.length < 1) return true
-
-    return pathArray.includes(nav.replace(/^\//, ''))
-  }
-
-  return { checkActiveNav }
-}

+ 0 - 29
src/hooks/use-is-collapsed.tsx

@@ -1,29 +0,0 @@
-import { useEffect } from 'react'
-import useLocalStorage from './use-local-storage'
-
-export default function useIsCollapsed() {
-  const [isCollapsed, setIsCollapsed] = useLocalStorage({
-    key: 'collapsed-sidebar',
-    defaultValue: false,
-  })
-
-  useEffect(() => {
-    const handleResize = () => {
-      // Update isCollapsed based on window.innerWidth
-      setIsCollapsed(window.innerWidth < 768 ? false : isCollapsed)
-    }
-
-    // Initial setup
-    handleResize()
-
-    // Add event listener for window resize
-    window.addEventListener('resize', handleResize)
-
-    // Cleanup event listener on component unmount
-    return () => {
-      window.removeEventListener('resize', handleResize)
-    }
-  }, [isCollapsed, setIsCollapsed])
-
-  return [isCollapsed, setIsCollapsed] as const
-}

+ 0 - 22
src/hooks/use-local-storage.tsx

@@ -1,22 +0,0 @@
-import { useEffect, useState } from 'react'
-
-interface LocalStorageProps<T> {
-  key: string
-  defaultValue: T
-}
-
-export default function useLocalStorage<T>({
-  key,
-  defaultValue,
-}: LocalStorageProps<T>) {
-  const [value, setValue] = useState<T>(() => {
-    const storedValue = localStorage.getItem(key)
-    return storedValue !== null ? (JSON.parse(storedValue) as T) : defaultValue
-  })
-
-  useEffect(() => {
-    localStorage.setItem(key, JSON.stringify(value))
-  }, [value, key])
-
-  return [value, setValue] as const
-}

Неке датотеке нису приказане због велике количине промена