From 87e0577ff730c6672fe4c5f1ca61f280a9968fad Mon Sep 17 00:00:00 2001
From: SomeCodecat <88855796+SomeCodecat@users.noreply.github.com>
Date: Mon, 16 Jun 2025 13:01:20 +0200
Subject: [PATCH 01/16] feat: add Radix UI components and implement sidebar
functionality
- Added new Radix UI components: Dialog, Tooltip, Separator, and updated existing components.
- Introduced a Sidebar component with collapsible functionality and mobile responsiveness.
- Implemented a custom hook `useIsMobile` to manage mobile state.
- Updated package dependencies in package.json and yarn.lock for new components.
- Created utility components such as Button, Skeleton, and Input for consistent styling.
feat: add AppSidebar component with collapsible functionality and sidebar menu
- Introduced AppSidebar component for a customizable sidebar layout.
- Implemented collapsible sections using Radix UI's Collapsible component.
- Added sidebar menu items with icons and links for navigation.
- Created Sidebar UI components including SidebarHeader, SidebarFooter, and SidebarMenu.
- Integrated ThemePicker for theme selection within the sidebar.
- Updated sidebar styles and layout for better responsiveness.
chore: add @radix-ui/react-collapsible dependency
- Added @radix-ui/react-collapsible package to manage collapsible UI elements.
---
package.json | 9 +-
src/app/{ => (main)}/home/page.tsx | 4 +-
src/app/(main)/layout.tsx | 23 +
src/app/globals.css | 18 +-
src/components/custom-ui/app-sidebar.tsx | 147 ++++
src/components/misc/header.tsx | 23 +
src/components/misc/logo.tsx | 1 +
src/components/ui/collapsible.tsx | 33 +
src/components/ui/separator.tsx | 7 +-
src/components/ui/sheet.tsx | 139 ++++
src/components/ui/sidebar.tsx | 725 +++++++++++++++++++
src/components/ui/skeleton.tsx | 13 +
src/components/ui/tooltip.tsx | 61 ++
src/components/wrappers/sidebar-provider.tsx | 23 +
src/hooks/use-mobile.ts | 19 +
yarn.lock | 167 ++++-
16 files changed, 1358 insertions(+), 54 deletions(-)
rename src/app/{ => (main)}/home/page.tsx (81%)
create mode 100644 src/app/(main)/layout.tsx
create mode 100644 src/components/custom-ui/app-sidebar.tsx
create mode 100644 src/components/misc/header.tsx
create mode 100644 src/components/ui/collapsible.tsx
create mode 100644 src/components/ui/sheet.tsx
create mode 100644 src/components/ui/sidebar.tsx
create mode 100644 src/components/ui/skeleton.tsx
create mode 100644 src/components/ui/tooltip.tsx
create mode 100644 src/components/wrappers/sidebar-provider.tsx
create mode 100644 src/hooks/use-mobile.ts
diff --git a/package.json b/package.json
index c5c77fb..c1273c1 100644
--- a/package.json
+++ b/package.json
@@ -27,20 +27,23 @@
"@fortawesome/react-fontawesome": "^0.2.2",
"@hookform/resolvers": "^5.0.1",
"@prisma/client": "^6.9.0",
+ "@radix-ui/react-collapsible": "^1.1.11",
+ "@radix-ui/react-dialog": "^1.1.14",
"@radix-ui/react-dropdown-menu": "^2.1.14",
"@radix-ui/react-hover-card": "^1.1.13",
"@radix-ui/react-label": "^2.1.6",
"@radix-ui/react-scroll-area": "^1.2.8",
"@radix-ui/react-select": "^2.2.4",
- "@radix-ui/react-separator": "^1.1.6",
- "@radix-ui/react-slot": "^1.2.2",
+ "@radix-ui/react-separator": "^1.1.7",
+ "@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.2.4",
"@radix-ui/react-tabs": "^1.1.11",
+ "@radix-ui/react-tooltip": "^1.2.7",
"@tanstack/react-query": "^5.80.7",
"bcryptjs": "^3.0.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
- "lucide-react": "^0.511.0",
+ "lucide-react": "^0.515.0",
"next": "15.4.0-canary.92",
"next-auth": "^5.0.0-beta.25",
"next-themes": "^0.4.6",
diff --git a/src/app/home/page.tsx b/src/app/(main)/home/page.tsx
similarity index 81%
rename from src/app/home/page.tsx
rename to src/app/(main)/home/page.tsx
index 77f3cf8..c381c03 100644
--- a/src/app/home/page.tsx
+++ b/src/app/(main)/home/page.tsx
@@ -1,15 +1,13 @@
'use client';
import { RedirectButton } from '@/components/buttons/redirect-button';
-import { ThemePicker } from '@/components/misc/theme-picker';
import { useGetApiUserMe } from '@/generated/api/user/user';
export default function Home() {
const { data, isLoading } = useGetApiUserMe();
return (
-
-
{}
+
Hello{' '}
diff --git a/src/app/(main)/layout.tsx b/src/app/(main)/layout.tsx
new file mode 100644
index 0000000..7106e70
--- /dev/null
+++ b/src/app/(main)/layout.tsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import { cookies } from 'next/headers';
+
+import { AppSidebar } from '@/components/custom-ui/app-sidebar';
+import SidebarProviderWrapper from '@/components/wrappers/sidebar-provider';
+import Header from '@/components/misc/header';
+
+export default async function Layout({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ const cookieStore = await cookies();
+ const defaultOpen = cookieStore.get('sidebar_state')?.value === 'true';
+ return (
+ <>
+
+
+
+
+ >
+ );
+}
diff --git a/src/app/globals.css b/src/app/globals.css
index f85cb2f..a5f7eaf 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -55,6 +55,8 @@
--card: var(--neutral-800);
+ --sidebar-width-icon: 32px;
+
/* ------------------- */
--foreground: oklch(0.13 0.028 261.692);
@@ -95,17 +97,17 @@
--chart-5: oklch(0.769 0.188 70.08);
- --sidebar: oklch(0.985 0.002 247.839);
+ --sidebar: var(--background);
- --sidebar-foreground: oklch(0.13 0.028 261.692);
+ --sidebar-foreground: var(--text);
--sidebar-primary: oklch(0.21 0.034 264.665);
- --sidebar-primary-foreground: oklch(0.985 0.002 247.839);
+ --sidebar-primary-foreground: var(--text);
--sidebar-accent: oklch(0.967 0.003 264.542);
- --sidebar-accent-foreground: oklch(0.21 0.034 264.665);
+ --sidebar-accent-foreground: var(--text);
--sidebar-border: oklch(0.928 0.006 264.531);
@@ -339,17 +341,17 @@
--chart-5: oklch(0.645 0.246 16.439);
- --sidebar: oklch(0.21 0.034 264.665);
+ --sidebar: var(--background);
- --sidebar-foreground: oklch(0.985 0.002 247.839);
+ --sidebar-foreground: var(--text);
--sidebar-primary: oklch(0.488 0.243 264.376);
- --sidebar-primary-foreground: oklch(0.985 0.002 247.839);
+ --sidebar-primary-foreground: var(--text);
--sidebar-accent: oklch(0.278 0.033 256.848);
- --sidebar-accent-foreground: oklch(0.985 0.002 247.839);
+ --sidebar-accent-foreground: var(--text);
--sidebar-border: oklch(1 0 0 / 10%);
diff --git a/src/components/custom-ui/app-sidebar.tsx b/src/components/custom-ui/app-sidebar.tsx
new file mode 100644
index 0000000..b279c73
--- /dev/null
+++ b/src/components/custom-ui/app-sidebar.tsx
@@ -0,0 +1,147 @@
+'use client';
+
+import React from 'react';
+import {
+ Sidebar,
+ SidebarContent,
+ SidebarFooter,
+ SidebarGroup,
+ SidebarGroupAction,
+ SidebarGroupContent,
+ SidebarGroupLabel,
+ SidebarHeader,
+ SidebarInput,
+ SidebarInset,
+ SidebarMenu,
+ SidebarMenuAction,
+ SidebarMenuBadge,
+ SidebarMenuButton,
+ SidebarMenuItem,
+ SidebarMenuSkeleton,
+ SidebarMenuSub,
+ SidebarMenuSubButton,
+ SidebarMenuSubItem,
+ SidebarProvider,
+ SidebarRail,
+ SidebarSeparator,
+ SidebarTrigger,
+ useSidebar,
+} from '@/components/ui/sidebar';
+
+import { ChevronDown } from 'lucide-react';
+import {
+ Collapsible,
+ CollapsibleContent,
+ CollapsibleTrigger,
+} from '@/components/ui/collapsible';
+
+import Logo from '@/components/misc/logo';
+
+import Link from 'next/link';
+
+import { ThemePicker } from '@/components/misc/theme-picker';
+
+import {
+ Star,
+ CalendarDays,
+ User,
+ Users,
+ CalendarClock,
+ CalendarPlus,
+} from 'lucide-react';
+
+const items = [
+ {
+ title: 'Calendar',
+ url: '#',
+ icon: CalendarDays,
+ },
+ {
+ title: 'Friends',
+ url: '#',
+ icon: User,
+ },
+ {
+ title: 'Groups',
+ url: '#',
+ icon: Users,
+ },
+ {
+ title: 'Events',
+ url: '#',
+ icon: CalendarClock,
+ },
+];
+
+export function AppSidebar() {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+ {' '}
+
+ Favorites
+
+
+
+
+
+
+
+
+
+
+
+
+ {items.map((item) => (
+
+
+
+
+
+ {item.title}
+
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+ New Event
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/components/misc/header.tsx b/src/components/misc/header.tsx
new file mode 100644
index 0000000..dbf8a1f
--- /dev/null
+++ b/src/components/misc/header.tsx
@@ -0,0 +1,23 @@
+import { SidebarTrigger } from '@/components/ui/sidebar';
+import { ThemePicker } from '@/components/misc/theme-picker';
+
+export default function Header({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ return (
+
+
+ {children}
+
+ );
+}
diff --git a/src/components/misc/logo.tsx b/src/components/misc/logo.tsx
index 129adef..739fc90 100644
--- a/src/components/misc/logo.tsx
+++ b/src/components/misc/logo.tsx
@@ -90,6 +90,7 @@ export default function Logo({
return (
) {
+ return ;
+}
+
+function CollapsibleTrigger({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function CollapsibleContent({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export { Collapsible, CollapsibleTrigger, CollapsibleContent };
diff --git a/src/components/ui/separator.tsx b/src/components/ui/separator.tsx
index 3b4f1ef..3234cdc 100644
--- a/src/components/ui/separator.tsx
+++ b/src/components/ui/separator.tsx
@@ -13,10 +13,13 @@ function Separator({
}: React.ComponentProps) {
return (
);
diff --git a/src/components/ui/sheet.tsx b/src/components/ui/sheet.tsx
new file mode 100644
index 0000000..84649ad
--- /dev/null
+++ b/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 { XIcon } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+function Sheet({ ...props }: React.ComponentProps) {
+ return
+}
+
+function SheetTrigger({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function SheetClose({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function SheetPortal({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function SheetOverlay({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SheetContent({
+ className,
+ children,
+ side = "right",
+ ...props
+}: React.ComponentProps & {
+ side?: "top" | "right" | "bottom" | "left"
+}) {
+ return (
+
+
+
+ {children}
+
+
+ Close
+
+
+
+ )
+}
+
+function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SheetTitle({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SheetDescription({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export {
+ Sheet,
+ SheetTrigger,
+ SheetClose,
+ SheetContent,
+ SheetHeader,
+ SheetFooter,
+ SheetTitle,
+ SheetDescription,
+}
diff --git a/src/components/ui/sidebar.tsx b/src/components/ui/sidebar.tsx
new file mode 100644
index 0000000..6b68b8f
--- /dev/null
+++ b/src/components/ui/sidebar.tsx
@@ -0,0 +1,725 @@
+'use client';
+
+import * as React from 'react';
+import { Slot } from '@radix-ui/react-slot';
+import { cva, VariantProps } from 'class-variance-authority';
+import { PanelLeftIcon } from 'lucide-react';
+
+import { useIsMobile } from '@/hooks/use-mobile';
+import { cn } from '@/lib/utils';
+import { Button } from '@/components/ui/button';
+import { Input } from '@/components/ui/input';
+import { Separator } from '@/components/ui/separator';
+import {
+ Sheet,
+ SheetContent,
+ SheetDescription,
+ SheetHeader,
+ SheetTitle,
+} from '@/components/ui/sheet';
+import { Skeleton } from '@/components/ui/skeleton';
+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 = '4rem';
+const SIDEBAR_KEYBOARD_SHORTCUT = 'b';
+
+type SidebarContextProps = {
+ state: 'expanded' | 'collapsed';
+ open: boolean;
+ setOpen: (open: boolean) => void;
+ openMobile: boolean;
+ setOpenMobile: (open: boolean) => void;
+ isMobile: boolean;
+ toggleSidebar: () => void;
+};
+
+const SidebarContext = React.createContext(null);
+
+function useSidebar() {
+ const context = React.useContext(SidebarContext);
+ if (!context) {
+ throw new Error('useSidebar must be used within a SidebarProvider.');
+ }
+
+ return context;
+}
+
+function SidebarProvider({
+ defaultOpen = true,
+ open: openProp,
+ onOpenChange: setOpenProp,
+ className,
+ style,
+ children,
+ ...props
+}: React.ComponentProps<'div'> & {
+ defaultOpen?: boolean;
+ open?: boolean;
+ onOpenChange?: (open: boolean) => void;
+}) {
+ 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(
+ () => ({
+ state,
+ open,
+ setOpen,
+ isMobile,
+ openMobile,
+ setOpenMobile,
+ toggleSidebar,
+ }),
+ [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar],
+ );
+
+ return (
+
+
+
+ {children}
+
+
+
+ );
+}
+
+function Sidebar({
+ side = 'left',
+ variant = 'sidebar',
+ collapsible = 'offcanvas',
+ className,
+ children,
+ ...props
+}: React.ComponentProps<'div'> & {
+ side?: 'left' | 'right';
+ variant?: 'sidebar' | 'floating' | 'inset';
+ collapsible?: 'offcanvas' | 'icon' | 'none';
+}) {
+ const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
+
+ if (collapsible === 'none') {
+ return (
+
+ {children}
+
+ );
+ }
+
+ if (isMobile) {
+ return (
+
+
+
+ Sidebar
+ Displays the mobile sidebar.
+
+ {children}
+
+
+ );
+ }
+
+ return (
+
+ {/* This is what handles the sidebar gap on desktop */}
+
+
+
+ );
+}
+
+function SidebarTrigger({
+ className,
+ onClick,
+ ...props
+}: React.ComponentProps) {
+ const { toggleSidebar } = useSidebar();
+
+ return (
+
+ );
+}
+
+function SidebarRail({ className, ...props }: React.ComponentProps<'button'>) {
+ const { toggleSidebar } = useSidebar();
+
+ return (
+
+ );
+}
+
+function SidebarInset({ className, ...props }: React.ComponentProps<'main'>) {
+ return (
+
+ );
+}
+
+function SidebarInput({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function SidebarHeader({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function SidebarFooter({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function SidebarSeparator({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function SidebarContent({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function SidebarGroup({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function SidebarGroupLabel({
+ className,
+ asChild = false,
+ ...props
+}: React.ComponentProps<'div'> & { asChild?: boolean }) {
+ const Comp = asChild ? Slot : 'div';
+
+ return (
+ svg]:size-4 [&>svg]:shrink-0 ml-[7.5px]',
+ className,
+ )}
+ {...props}
+ />
+ );
+}
+
+function SidebarGroupAction({
+ className,
+ asChild = false,
+ ...props
+}: React.ComponentProps<'button'> & { asChild?: boolean }) {
+ const Comp = asChild ? Slot : 'button';
+
+ return (
+ svg]:size-4 [&>svg]:shrink-0',
+ // Increases the hit area of the button on mobile.
+ 'after:absolute after:-inset-2 md:after:hidden',
+ 'group-data-[collapsible=icon]:hidden',
+ className,
+ )}
+ {...props}
+ />
+ );
+}
+
+function SidebarGroupContent({
+ className,
+ ...props
+}: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function SidebarMenu({ className, ...props }: React.ComponentProps<'ul'>) {
+ return (
+
+ );
+}
+
+function SidebarMenuItem({ className, ...props }: React.ComponentProps<'li'>) {
+ return (
+
+ );
+}
+
+const sidebarMenuButtonVariants = cva(
+ 'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden 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! p-0 ml-[15.5px] [&>span:last-child]:truncate [&>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',
+ },
+ },
+);
+
+function SidebarMenuButton({
+ asChild = false,
+ isActive = false,
+ variant = 'default',
+ size = 'default',
+ tooltip,
+ className,
+ ...props
+}: React.ComponentProps<'button'> & {
+ asChild?: boolean;
+ isActive?: boolean;
+ tooltip?: string | React.ComponentProps;
+} & VariantProps) {
+ const Comp = asChild ? Slot : 'button';
+ const { isMobile, state } = useSidebar();
+
+ const button = (
+
+ );
+
+ if (!tooltip) {
+ return button;
+ }
+
+ if (typeof tooltip === 'string') {
+ tooltip = {
+ children: tooltip,
+ };
+ }
+
+ return (
+
+ {button}
+
+
+ );
+}
+
+function SidebarMenuAction({
+ className,
+ asChild = false,
+ showOnHover = false,
+ ...props
+}: React.ComponentProps<'button'> & {
+ asChild?: boolean;
+ showOnHover?: boolean;
+}) {
+ const Comp = asChild ? Slot : 'button';
+
+ return (
+ svg]:size-4 [&>svg]:shrink-0',
+ // Increases the hit area of the button on mobile.
+ 'after:absolute after:-inset-2 md:after: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 &&
+ 'peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0',
+ className,
+ )}
+ {...props}
+ />
+ );
+}
+
+function SidebarMenuBadge({
+ className,
+ ...props
+}: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function SidebarMenuSkeleton({
+ className,
+ showIcon = false,
+ ...props
+}: React.ComponentProps<'div'> & {
+ showIcon?: boolean;
+}) {
+ // Random width between 50 to 90%.
+ const width = React.useMemo(() => {
+ return `${Math.floor(Math.random() * 40) + 50}%`;
+ }, []);
+
+ return (
+
+ {showIcon && (
+
+ )}
+
+
+ );
+}
+
+function SidebarMenuSub({ className, ...props }: React.ComponentProps<'ul'>) {
+ return (
+
+ );
+}
+
+function SidebarMenuSubItem({
+ className,
+ ...props
+}: React.ComponentProps<'li'>) {
+ return (
+
+ );
+}
+
+function SidebarMenuSubButton({
+ asChild = false,
+ size = 'md',
+ isActive = false,
+ className,
+ ...props
+}: React.ComponentProps<'a'> & {
+ asChild?: boolean;
+ size?: 'sm' | 'md';
+ isActive?: boolean;
+}) {
+ const Comp = asChild ? Slot : 'a';
+
+ return (
+ svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-hidden focus-visible:ring-2 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',
+ '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}
+ />
+ );
+}
+
+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,
+};
diff --git a/src/components/ui/skeleton.tsx b/src/components/ui/skeleton.tsx
new file mode 100644
index 0000000..32ea0ef
--- /dev/null
+++ b/src/components/ui/skeleton.tsx
@@ -0,0 +1,13 @@
+import { cn } from "@/lib/utils"
+
+function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+export { Skeleton }
diff --git a/src/components/ui/tooltip.tsx b/src/components/ui/tooltip.tsx
new file mode 100644
index 0000000..4ee26b3
--- /dev/null
+++ b/src/components/ui/tooltip.tsx
@@ -0,0 +1,61 @@
+"use client"
+
+import * as React from "react"
+import * as TooltipPrimitive from "@radix-ui/react-tooltip"
+
+import { cn } from "@/lib/utils"
+
+function TooltipProvider({
+ delayDuration = 0,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function Tooltip({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+function TooltipTrigger({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function TooltipContent({
+ className,
+ sideOffset = 0,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+ {children}
+
+
+
+ )
+}
+
+export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
diff --git a/src/components/wrappers/sidebar-provider.tsx b/src/components/wrappers/sidebar-provider.tsx
new file mode 100644
index 0000000..3873fa4
--- /dev/null
+++ b/src/components/wrappers/sidebar-provider.tsx
@@ -0,0 +1,23 @@
+'use client';
+
+import React from 'react';
+import { SidebarProvider } from '../ui/sidebar';
+
+export default function SidebarProviderWrapper({
+ defaultOpen,
+ children,
+}: {
+ defaultOpen: boolean;
+ children: React.ReactNode;
+}) {
+ const [open, setOpen] = React.useState(defaultOpen);
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/hooks/use-mobile.ts b/src/hooks/use-mobile.ts
new file mode 100644
index 0000000..2b0fe1d
--- /dev/null
+++ b/src/hooks/use-mobile.ts
@@ -0,0 +1,19 @@
+import * as React from "react"
+
+const MOBILE_BREAKPOINT = 768
+
+export function useIsMobile() {
+ const [isMobile, setIsMobile] = React.useState(undefined)
+
+ React.useEffect(() => {
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
+ const onChange = () => {
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
+ }
+ mql.addEventListener("change", onChange)
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
+ return () => mql.removeEventListener("change", onChange)
+ }, [])
+
+ return !!isMobile
+}
diff --git a/yarn.lock b/yarn.lock
index 56de1d1..e1ff28c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -543,15 +543,15 @@ __metadata:
linkType: hard
"@gerrit0/mini-shiki@npm:^3.2.2":
- version: 3.6.0
- resolution: "@gerrit0/mini-shiki@npm:3.6.0"
+ version: 3.7.0
+ resolution: "@gerrit0/mini-shiki@npm:3.7.0"
dependencies:
- "@shikijs/engine-oniguruma": "npm:^3.6.0"
- "@shikijs/langs": "npm:^3.6.0"
- "@shikijs/themes": "npm:^3.6.0"
- "@shikijs/types": "npm:^3.6.0"
+ "@shikijs/engine-oniguruma": "npm:^3.7.0"
+ "@shikijs/langs": "npm:^3.7.0"
+ "@shikijs/themes": "npm:^3.7.0"
+ "@shikijs/types": "npm:^3.7.0"
"@shikijs/vscode-textmate": "npm:^10.0.2"
- checksum: 10c0/347456c9da8a1fadd3c1f63097da459a5f930ef4bca6431cce913a379012c551e061d0a94ff7a0f307215b87f2418b7c198a55fba888fc97fb02ab36247adf6b
+ checksum: 10c0/eb3f4900d841338077d839ebbc7f8722b13876a586cff7abc73295e956683724dd3371a9f990900184a2d069461965951b2604d677991badf3474262e7811384
languageName: node
linkType: hard
@@ -1299,6 +1299,32 @@ __metadata:
languageName: node
linkType: hard
+"@radix-ui/react-collapsible@npm:^1.1.11":
+ version: 1.1.11
+ resolution: "@radix-ui/react-collapsible@npm:1.1.11"
+ dependencies:
+ "@radix-ui/primitive": "npm:1.1.2"
+ "@radix-ui/react-compose-refs": "npm:1.1.2"
+ "@radix-ui/react-context": "npm:1.1.2"
+ "@radix-ui/react-id": "npm:1.1.1"
+ "@radix-ui/react-presence": "npm:1.1.4"
+ "@radix-ui/react-primitive": "npm:2.1.3"
+ "@radix-ui/react-use-controllable-state": "npm:1.2.2"
+ "@radix-ui/react-use-layout-effect": "npm:1.1.1"
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+ checksum: 10c0/fa2de539ef06e2b2d18acebb12a34ce1534ca88bd484b7359aac05534d1e551fe83eaafbf60915c00161bb370f0dc9fc303903133510dea0a59fd018155b7db5
+ languageName: node
+ linkType: hard
+
"@radix-ui/react-collection@npm:1.1.7":
version: 1.1.7
resolution: "@radix-ui/react-collection@npm:1.1.7"
@@ -1347,6 +1373,38 @@ __metadata:
languageName: node
linkType: hard
+"@radix-ui/react-dialog@npm:^1.1.14":
+ version: 1.1.14
+ resolution: "@radix-ui/react-dialog@npm:1.1.14"
+ dependencies:
+ "@radix-ui/primitive": "npm:1.1.2"
+ "@radix-ui/react-compose-refs": "npm:1.1.2"
+ "@radix-ui/react-context": "npm:1.1.2"
+ "@radix-ui/react-dismissable-layer": "npm:1.1.10"
+ "@radix-ui/react-focus-guards": "npm:1.1.2"
+ "@radix-ui/react-focus-scope": "npm:1.1.7"
+ "@radix-ui/react-id": "npm:1.1.1"
+ "@radix-ui/react-portal": "npm:1.1.9"
+ "@radix-ui/react-presence": "npm:1.1.4"
+ "@radix-ui/react-primitive": "npm:2.1.3"
+ "@radix-ui/react-slot": "npm:1.2.3"
+ "@radix-ui/react-use-controllable-state": "npm:1.2.2"
+ aria-hidden: "npm:^1.2.4"
+ react-remove-scroll: "npm:^2.6.3"
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+ checksum: 10c0/ab7bc783510ed8fccfe91020b214f4a571d5a1d46d398faa33f4c151bc9f586c47483b307e72b67687b06694c194b3aa80dd1de728460fa765db9f3057690ba3
+ languageName: node
+ linkType: hard
+
"@radix-ui/react-direction@npm:1.1.1":
version: 1.1.1
resolution: "@radix-ui/react-direction@npm:1.1.1"
@@ -1719,7 +1777,7 @@ __metadata:
languageName: node
linkType: hard
-"@radix-ui/react-separator@npm:^1.1.6":
+"@radix-ui/react-separator@npm:^1.1.7":
version: 1.1.7
resolution: "@radix-ui/react-separator@npm:1.1.7"
dependencies:
@@ -1738,7 +1796,7 @@ __metadata:
languageName: node
linkType: hard
-"@radix-ui/react-slot@npm:1.2.3, @radix-ui/react-slot@npm:^1.2.2":
+"@radix-ui/react-slot@npm:1.2.3, @radix-ui/react-slot@npm:^1.2.3":
version: 1.2.3
resolution: "@radix-ui/react-slot@npm:1.2.3"
dependencies:
@@ -1804,6 +1862,36 @@ __metadata:
languageName: node
linkType: hard
+"@radix-ui/react-tooltip@npm:^1.2.7":
+ version: 1.2.7
+ resolution: "@radix-ui/react-tooltip@npm:1.2.7"
+ dependencies:
+ "@radix-ui/primitive": "npm:1.1.2"
+ "@radix-ui/react-compose-refs": "npm:1.1.2"
+ "@radix-ui/react-context": "npm:1.1.2"
+ "@radix-ui/react-dismissable-layer": "npm:1.1.10"
+ "@radix-ui/react-id": "npm:1.1.1"
+ "@radix-ui/react-popper": "npm:1.2.7"
+ "@radix-ui/react-portal": "npm:1.1.9"
+ "@radix-ui/react-presence": "npm:1.1.4"
+ "@radix-ui/react-primitive": "npm:2.1.3"
+ "@radix-ui/react-slot": "npm:1.2.3"
+ "@radix-ui/react-use-controllable-state": "npm:1.2.2"
+ "@radix-ui/react-visually-hidden": "npm:1.2.3"
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+ checksum: 10c0/28798d576c6ffec4f11120cd563aa9d5ab9afb9a37dc18778176442756d026c8c46eec1ddc647b2b5914045495fcb89f82530106e91acb55776b7d6b1a10fb57
+ languageName: node
+ linkType: hard
+
"@radix-ui/react-use-callback-ref@npm:1.1.1":
version: 1.1.1
resolution: "@radix-ui/react-use-callback-ref@npm:1.1.1"
@@ -1966,7 +2054,7 @@ __metadata:
languageName: node
linkType: hard
-"@shikijs/engine-oniguruma@npm:^3.6.0":
+"@shikijs/engine-oniguruma@npm:^3.7.0":
version: 3.7.0
resolution: "@shikijs/engine-oniguruma@npm:3.7.0"
dependencies:
@@ -1976,7 +2064,7 @@ __metadata:
languageName: node
linkType: hard
-"@shikijs/langs@npm:^3.6.0":
+"@shikijs/langs@npm:^3.7.0":
version: 3.7.0
resolution: "@shikijs/langs@npm:3.7.0"
dependencies:
@@ -1985,7 +2073,7 @@ __metadata:
languageName: node
linkType: hard
-"@shikijs/themes@npm:^3.6.0":
+"@shikijs/themes@npm:^3.7.0":
version: 3.7.0
resolution: "@shikijs/themes@npm:3.7.0"
dependencies:
@@ -1994,7 +2082,7 @@ __metadata:
languageName: node
linkType: hard
-"@shikijs/types@npm:3.7.0, @shikijs/types@npm:^3.6.0":
+"@shikijs/types@npm:3.7.0, @shikijs/types@npm:^3.7.0":
version: 3.7.0
resolution: "@shikijs/types@npm:3.7.0"
dependencies:
@@ -3662,7 +3750,7 @@ __metadata:
languageName: node
linkType: hard
-"array-includes@npm:^3.1.6, array-includes@npm:^3.1.8":
+"array-includes@npm:^3.1.6, array-includes@npm:^3.1.8, array-includes@npm:^3.1.9":
version: 3.1.9
resolution: "array-includes@npm:3.1.9"
dependencies:
@@ -3699,7 +3787,7 @@ __metadata:
languageName: node
linkType: hard
-"array.prototype.findlastindex@npm:^1.2.5":
+"array.prototype.findlastindex@npm:^1.2.6":
version: 1.2.6
resolution: "array.prototype.findlastindex@npm:1.2.6"
dependencies:
@@ -3714,7 +3802,7 @@ __metadata:
languageName: node
linkType: hard
-"array.prototype.flat@npm:^1.3.1, array.prototype.flat@npm:^1.3.2":
+"array.prototype.flat@npm:^1.3.1, array.prototype.flat@npm:^1.3.3":
version: 1.3.3
resolution: "array.prototype.flat@npm:1.3.3"
dependencies:
@@ -4792,7 +4880,7 @@ __metadata:
languageName: node
linkType: hard
-"eslint-module-utils@npm:^2.12.0":
+"eslint-module-utils@npm:^2.12.1":
version: 2.12.1
resolution: "eslint-module-utils@npm:2.12.1"
dependencies:
@@ -4805,31 +4893,31 @@ __metadata:
linkType: hard
"eslint-plugin-import@npm:^2.31.0":
- version: 2.31.0
- resolution: "eslint-plugin-import@npm:2.31.0"
+ version: 2.32.0
+ resolution: "eslint-plugin-import@npm:2.32.0"
dependencies:
"@rtsao/scc": "npm:^1.1.0"
- array-includes: "npm:^3.1.8"
- array.prototype.findlastindex: "npm:^1.2.5"
- array.prototype.flat: "npm:^1.3.2"
- array.prototype.flatmap: "npm:^1.3.2"
+ array-includes: "npm:^3.1.9"
+ array.prototype.findlastindex: "npm:^1.2.6"
+ array.prototype.flat: "npm:^1.3.3"
+ array.prototype.flatmap: "npm:^1.3.3"
debug: "npm:^3.2.7"
doctrine: "npm:^2.1.0"
eslint-import-resolver-node: "npm:^0.3.9"
- eslint-module-utils: "npm:^2.12.0"
+ eslint-module-utils: "npm:^2.12.1"
hasown: "npm:^2.0.2"
- is-core-module: "npm:^2.15.1"
+ is-core-module: "npm:^2.16.1"
is-glob: "npm:^4.0.3"
minimatch: "npm:^3.1.2"
object.fromentries: "npm:^2.0.8"
object.groupby: "npm:^1.0.3"
- object.values: "npm:^1.2.0"
+ object.values: "npm:^1.2.1"
semver: "npm:^6.3.1"
- string.prototype.trimend: "npm:^1.0.8"
+ string.prototype.trimend: "npm:^1.0.9"
tsconfig-paths: "npm:^3.15.0"
peerDependencies:
eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9
- checksum: 10c0/e21d116ddd1900e091ad120b3eb68c5dd5437fe2c930f1211781cd38b246f090a6b74d5f3800b8255a0ed29782591521ad44eb21c5534960a8f1fb4040fd913a
+ checksum: 10c0/bfb1b8fc8800398e62ddfefbf3638d185286edfed26dfe00875cc2846d954491b4f5112457831588b757fa789384e1ae585f812614c4797f0499fa234fd4a48b
languageName: node
linkType: hard
@@ -5758,7 +5846,7 @@ __metadata:
languageName: node
linkType: hard
-"is-core-module@npm:^2.13.0, is-core-module@npm:^2.15.1, is-core-module@npm:^2.16.0":
+"is-core-module@npm:^2.13.0, is-core-module@npm:^2.16.0, is-core-module@npm:^2.16.1":
version: 2.16.1
resolution: "is-core-module@npm:2.16.1"
dependencies:
@@ -6454,12 +6542,12 @@ __metadata:
languageName: node
linkType: hard
-"lucide-react@npm:^0.511.0":
- version: 0.511.0
- resolution: "lucide-react@npm:0.511.0"
+"lucide-react@npm:^0.515.0":
+ version: 0.515.0
+ resolution: "lucide-react@npm:0.515.0"
peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
- checksum: 10c0/bf09dd73cf2233abea90506ad31a91739555d761062722acbe045cb73e274f035b196472de0971a8a8f0645b2b54e3f21b8c1980fe87c909ca93171a9c28428a
+ checksum: 10c0/00485e09ab3d0bbb34797b1f368c269e8708522b6e2f46fd84dd5bd99741546487be9a65a260f274e8049b81cc37687566e26132f5752352c8d9bc8e5d0b3dea
languageName: node
linkType: hard
@@ -6549,15 +6637,18 @@ __metadata:
"@fortawesome/react-fontawesome": "npm:^0.2.2"
"@hookform/resolvers": "npm:^5.0.1"
"@prisma/client": "npm:^6.9.0"
+ "@radix-ui/react-collapsible": "npm:^1.1.11"
+ "@radix-ui/react-dialog": "npm:^1.1.14"
"@radix-ui/react-dropdown-menu": "npm:^2.1.14"
"@radix-ui/react-hover-card": "npm:^1.1.13"
"@radix-ui/react-label": "npm:^2.1.6"
"@radix-ui/react-scroll-area": "npm:^1.2.8"
"@radix-ui/react-select": "npm:^2.2.4"
- "@radix-ui/react-separator": "npm:^1.1.6"
- "@radix-ui/react-slot": "npm:^1.2.2"
+ "@radix-ui/react-separator": "npm:^1.1.7"
+ "@radix-ui/react-slot": "npm:^1.2.3"
"@radix-ui/react-switch": "npm:^1.2.4"
"@radix-ui/react-tabs": "npm:^1.1.11"
+ "@radix-ui/react-tooltip": "npm:^1.2.7"
"@tailwindcss/postcss": "npm:4.1.10"
"@tanstack/react-query": "npm:^5.80.7"
"@types/node": "npm:22.15.32"
@@ -6572,7 +6663,7 @@ __metadata:
eslint: "npm:9.29.0"
eslint-config-next: "npm:15.3.4"
eslint-config-prettier: "npm:10.1.5"
- lucide-react: "npm:^0.511.0"
+ lucide-react: "npm:^0.515.0"
next: "npm:15.4.0-canary.92"
next-auth: "npm:^5.0.0-beta.25"
next-themes: "npm:^0.4.6"
@@ -7185,7 +7276,7 @@ __metadata:
languageName: node
linkType: hard
-"object.values@npm:^1.1.6, object.values@npm:^1.2.0, object.values@npm:^1.2.1":
+"object.values@npm:^1.1.6, object.values@npm:^1.2.1":
version: 1.2.1
resolution: "object.values@npm:1.2.1"
dependencies:
@@ -8569,7 +8660,7 @@ __metadata:
languageName: node
linkType: hard
-"string.prototype.trimend@npm:^1.0.8, string.prototype.trimend@npm:^1.0.9":
+"string.prototype.trimend@npm:^1.0.9":
version: 1.0.9
resolution: "string.prototype.trimend@npm:1.0.9"
dependencies:
From 15015931d6ce0ca090b382aaf69d4977dd83f9b0 Mon Sep 17 00:00:00 2001
From: Micha
Date: Sun, 22 Jun 2025 22:39:46 +0200
Subject: [PATCH 02/16] fix: add new Logos for equal hight in Sidebar
---
src/assets/logo/logo-export.ts | 8 ++++----
src/assets/logo/new/logo_colored_combo_dark.svg | 17 +++++++++++++++++
.../logo/new/logo_colored_combo_light.svg | 17 +++++++++++++++++
.../logo/new/logo_colored_submark_dark.svg | 10 ++++++++++
.../logo/new/logo_colored_submark_light.svg | 10 ++++++++++
src/components/custom-ui/app-sidebar.tsx | 6 +++---
6 files changed, 61 insertions(+), 7 deletions(-)
create mode 100644 src/assets/logo/new/logo_colored_combo_dark.svg
create mode 100644 src/assets/logo/new/logo_colored_combo_light.svg
create mode 100644 src/assets/logo/new/logo_colored_submark_dark.svg
create mode 100644 src/assets/logo/new/logo_colored_submark_light.svg
diff --git a/src/assets/logo/logo-export.ts b/src/assets/logo/logo-export.ts
index 44681d3..17a0708 100644
--- a/src/assets/logo/logo-export.ts
+++ b/src/assets/logo/logo-export.ts
@@ -1,5 +1,5 @@
-export { default as logo_colored_combo_light } from '@/assets/logo/logo_colored_combo_light.svg';
-export { default as logo_colored_combo_dark } from '@/assets/logo/logo_colored_combo_dark.svg';
+export { default as logo_colored_combo_light } from '@/assets/logo/new/logo_colored_combo_light.svg';
+export { default as logo_colored_combo_dark } from '@/assets/logo/new/logo_colored_combo_dark.svg';
export { default as logo_colored_primary_light } from '@/assets/logo/logo_colored_primary_light.svg';
export { default as logo_colored_primary_dark } from '@/assets/logo/logo_colored_primary_dark.svg';
export { default as logo_colored_secondary_light } from '@/assets/logo/logo_colored_secondary_light.svg';
@@ -12,5 +12,5 @@ export { default as logo_mono_secondary_light } from '@/assets/logo/logo_mono_se
export { default as logo_mono_secondary_dark } from '@/assets/logo/logo_mono_secondary_dark.svg';
export { default as logo_mono_submark_light } from '@/assets/logo/logo_mono_submark_light.svg';
export { default as logo_mono_submark_dark } from '@/assets/logo/logo_mono_submark_dark.svg';
-export { default as logo_colored_submark_light } from '@/assets/logo/logo_colored_submark_light.svg';
-export { default as logo_colored_submark_dark } from '@/assets/logo/logo_colored_submark_dark.svg';
+export { default as logo_colored_submark_light } from '@/assets/logo/new/logo_colored_submark_light.svg';
+export { default as logo_colored_submark_dark } from '@/assets/logo/new/logo_colored_submark_dark.svg';
diff --git a/src/assets/logo/new/logo_colored_combo_dark.svg b/src/assets/logo/new/logo_colored_combo_dark.svg
new file mode 100644
index 0000000..ec4acae
--- /dev/null
+++ b/src/assets/logo/new/logo_colored_combo_dark.svg
@@ -0,0 +1,17 @@
+
diff --git a/src/assets/logo/new/logo_colored_combo_light.svg b/src/assets/logo/new/logo_colored_combo_light.svg
new file mode 100644
index 0000000..33e6c6d
--- /dev/null
+++ b/src/assets/logo/new/logo_colored_combo_light.svg
@@ -0,0 +1,17 @@
+
diff --git a/src/assets/logo/new/logo_colored_submark_dark.svg b/src/assets/logo/new/logo_colored_submark_dark.svg
new file mode 100644
index 0000000..efadbf0
--- /dev/null
+++ b/src/assets/logo/new/logo_colored_submark_dark.svg
@@ -0,0 +1,10 @@
+
diff --git a/src/assets/logo/new/logo_colored_submark_light.svg b/src/assets/logo/new/logo_colored_submark_light.svg
new file mode 100644
index 0000000..cc7b3e1
--- /dev/null
+++ b/src/assets/logo/new/logo_colored_submark_light.svg
@@ -0,0 +1,10 @@
+
diff --git a/src/components/custom-ui/app-sidebar.tsx b/src/components/custom-ui/app-sidebar.tsx
index b279c73..ae00c9b 100644
--- a/src/components/custom-ui/app-sidebar.tsx
+++ b/src/components/custom-ui/app-sidebar.tsx
@@ -81,14 +81,14 @@ export function AppSidebar() {
From 0cd88d6f07e58fffbb91a31a45e1d21e5a14e2d1 Mon Sep 17 00:00:00 2001
From: Maximilian Liebmann
Date: Sun, 22 Jun 2025 23:22:53 +0200
Subject: [PATCH 03/16] feat: enhance header with notification buttons and user
dropdown
- Updated header component to include notification buttons with icons.
- Introduced a new NavUser component for user-related actions in the sidebar.
- Added NotificationDot component for visual notification indicators.
- Created UserCard component to display user information.
- Implemented UserDropdown for user settings and logout functionality.
- Added Avatar component for user images with fallback support.
- Refactored Sheet and Tooltip components for consistency and improved styling.
- Introduced QueryProvider for managing React Query context.
- Updated SidebarProvider to use custom sidebar implementation.
- Enhanced mobile detection hook for better responsiveness.
- Updated dependencies in yarn.lock for new features and fixes.
feat: remove dot
---
package.json | 3 +-
src/app/globals.css | 3 +
src/app/layout.tsx | 2 +-
.../buttons/notification-button.tsx | 40 +++++++
src/components/custom-ui/app-sidebar.tsx | 32 +++--
src/components/{ui => custom-ui}/sidebar.tsx | 2 +-
src/components/misc/header.tsx | 34 +++++-
src/components/misc/nav-user.tsx | 110 ++++++++++++++++++
src/components/misc/notification-dot.tsx | 35 ++++++
src/components/misc/user-card.tsx | 29 +++++
src/components/misc/user-dropdown.tsx | 59 ++++++++++
src/components/ui/avatar.tsx | 53 +++++++++
src/components/ui/sheet.tsx | 90 +++++++-------
src/components/ui/skeleton.tsx | 12 +-
src/components/ui/tooltip.tsx | 30 ++---
.../{ => wrappers}/query-provider.tsx | 0
src/components/wrappers/sidebar-provider.tsx | 2 +-
src/hooks/use-mobile.ts | 24 ++--
yarn.lock | 45 ++++++-
19 files changed, 501 insertions(+), 104 deletions(-)
create mode 100644 src/components/buttons/notification-button.tsx
rename src/components/{ui => custom-ui}/sidebar.tsx (99%)
create mode 100644 src/components/misc/nav-user.tsx
create mode 100644 src/components/misc/notification-dot.tsx
create mode 100644 src/components/misc/user-card.tsx
create mode 100644 src/components/misc/user-dropdown.tsx
create mode 100644 src/components/ui/avatar.tsx
rename src/components/{ => wrappers}/query-provider.tsx (100%)
diff --git a/package.json b/package.json
index c1273c1..a9aa812 100644
--- a/package.json
+++ b/package.json
@@ -27,9 +27,10 @@
"@fortawesome/react-fontawesome": "^0.2.2",
"@hookform/resolvers": "^5.0.1",
"@prisma/client": "^6.9.0",
+ "@radix-ui/react-avatar": "^1.1.10",
"@radix-ui/react-collapsible": "^1.1.11",
"@radix-ui/react-dialog": "^1.1.14",
- "@radix-ui/react-dropdown-menu": "^2.1.14",
+ "@radix-ui/react-dropdown-menu": "^2.1.15",
"@radix-ui/react-hover-card": "^1.1.13",
"@radix-ui/react-label": "^2.1.6",
"@radix-ui/react-scroll-area": "^1.2.8",
diff --git a/src/app/globals.css b/src/app/globals.css
index a5f7eaf..93a24ce 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -33,6 +33,7 @@
--text-alt: var(--neutral-900);
--text-input: var(--text);
--text-muted-input: var(--neutral-450);
+ --text-muted: var(--neutral-300);
--muted-input: var(--neutral-600);
--background-disabled: var(--neutral-500);
--text-disabled: var(--neutral-700);
@@ -157,6 +158,7 @@
--color-text-alt: var(--text-alt);
--color-text-input: var(--text-input);
--color-text-muted-input: var(--text-muted-input);
+ --color-text-muted: var(--text-muted);
--color-muted-input: var(--muted-input);
--color-background-disabled: var(--neutral-500);
@@ -280,6 +282,7 @@
--text-alt: var(--neutral-900);
--text-input: var(--text);
--text-muted-input: var(--neutral-450);
+ --text-muted: var(--neutral-300);
--muted-input: var(--neutral-500);
--background-disabled: var(--neutral-500);
--text-disabled: var(--neutral-700);
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 201a730..af40867 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -2,7 +2,7 @@ import { ThemeProvider } from '@/components/wrappers/theme-provider';
import type { Metadata } from 'next';
import './globals.css';
-import { QueryProvider } from '@/components/query-provider';
+import { QueryProvider } from '@/components/wrappers/query-provider';
export const metadata: Metadata = {
title: 'MeetUp',
diff --git a/src/components/buttons/notification-button.tsx b/src/components/buttons/notification-button.tsx
new file mode 100644
index 0000000..0b718f9
--- /dev/null
+++ b/src/components/buttons/notification-button.tsx
@@ -0,0 +1,40 @@
+import { Button } from '@/components/ui/button';
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ // DropdownMenuGroup,
+ // DropdownMenuItem,
+ // DropdownMenuLabel,
+ // DropdownMenuPortal,
+ // DropdownMenuSeparator,
+ // DropdownMenuShortcut,
+ // DropdownMenuSub,
+ // DropdownMenuSubContent,
+ // DropdownMenuSubTrigger,
+ DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu';
+import { NDot, NotificationDot } from '@/components/misc/notification-dot';
+
+export function NotificationButton({
+ dotVariant,
+ children,
+ ...props
+}: {
+ dotVariant?: NDot;
+ children: React.ReactNode;
+} & React.ComponentProps) {
+ return (
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/custom-ui/app-sidebar.tsx b/src/components/custom-ui/app-sidebar.tsx
index ae00c9b..4861363 100644
--- a/src/components/custom-ui/app-sidebar.tsx
+++ b/src/components/custom-ui/app-sidebar.tsx
@@ -6,27 +6,27 @@ import {
SidebarContent,
SidebarFooter,
SidebarGroup,
- SidebarGroupAction,
+ // SidebarGroupAction,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
- SidebarInput,
- SidebarInset,
+ // SidebarInput,
+ // SidebarInset,
SidebarMenu,
- SidebarMenuAction,
- SidebarMenuBadge,
+ // SidebarMenuAction,
+ // SidebarMenuBadge,
SidebarMenuButton,
SidebarMenuItem,
- SidebarMenuSkeleton,
- SidebarMenuSub,
- SidebarMenuSubButton,
- SidebarMenuSubItem,
- SidebarProvider,
- SidebarRail,
- SidebarSeparator,
- SidebarTrigger,
- useSidebar,
-} from '@/components/ui/sidebar';
+ // SidebarMenuSkeleton,
+ // SidebarMenuSub,
+ // SidebarMenuSubButton,
+ // SidebarMenuSubItem,
+ // SidebarProvider,
+ // SidebarRail,
+ // SidebarSeparator,
+ // SidebarTrigger,
+ // useSidebar,
+} from '@/components/custom-ui/sidebar';
import { ChevronDown } from 'lucide-react';
import {
@@ -39,8 +39,6 @@ import Logo from '@/components/misc/logo';
import Link from 'next/link';
-import { ThemePicker } from '@/components/misc/theme-picker';
-
import {
Star,
CalendarDays,
diff --git a/src/components/ui/sidebar.tsx b/src/components/custom-ui/sidebar.tsx
similarity index 99%
rename from src/components/ui/sidebar.tsx
rename to src/components/custom-ui/sidebar.tsx
index 6b68b8f..11228cb 100644
--- a/src/components/ui/sidebar.tsx
+++ b/src/components/custom-ui/sidebar.tsx
@@ -466,7 +466,7 @@ function SidebarMenuItem({ className, ...props }: React.ComponentProps<'li'>) {
);
diff --git a/src/components/misc/header.tsx b/src/components/misc/header.tsx
index dbf8a1f..ed53953 100644
--- a/src/components/misc/header.tsx
+++ b/src/components/misc/header.tsx
@@ -1,5 +1,22 @@
-import { SidebarTrigger } from '@/components/ui/sidebar';
+import { SidebarTrigger } from '@/components/custom-ui/sidebar';
import { ThemePicker } from '@/components/misc/theme-picker';
+import { NotificationButton } from '@/components/buttons/notification-button';
+
+import { BellRing, Inbox } from 'lucide-react';
+import UserDropdown from '@/components/misc/user-dropdown';
+
+const items = [
+ {
+ title: 'Calendar',
+ url: '#',
+ icon: Inbox,
+ },
+ {
+ title: 'Friends',
+ url: '#',
+ icon: BellRing,
+ },
+];
export default function Header({
children,
@@ -8,13 +25,24 @@ export default function Header({
}>) {
return (
-
+
Search
-
+
+ {items.map((item) => (
+
+
+
+ ))}
+
{children}
diff --git a/src/components/misc/nav-user.tsx b/src/components/misc/nav-user.tsx
new file mode 100644
index 0000000..53ab582
--- /dev/null
+++ b/src/components/misc/nav-user.tsx
@@ -0,0 +1,110 @@
+'use client';
+
+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/custom-ui/sidebar';
+
+export function NavUser({
+ user,
+}: {
+ user: {
+ name: string;
+ email: string;
+ avatar: string;
+ };
+}) {
+ const { isMobile } = useSidebar();
+
+ return (
+
+
+
+
+
+
+
+ CN
+
+
+ {user.name}
+ {user.email}
+
+
+
+
+
+
+
+
+
+ CN
+
+
+ {user.name}
+ {user.email}
+
+
+
+
+
+
+
+ Upgrade to Pro
+
+
+
+
+
+
+ Account
+
+
+
+ Billing
+
+
+
+ Notifications
+
+
+
+
+
+ Log out
+
+
+
+
+
+ );
+}
diff --git a/src/components/misc/notification-dot.tsx b/src/components/misc/notification-dot.tsx
new file mode 100644
index 0000000..a918188
--- /dev/null
+++ b/src/components/misc/notification-dot.tsx
@@ -0,0 +1,35 @@
+import { cn } from '@/lib/utils';
+import { cva, type VariantProps } from 'class-variance-authority';
+import { CircleSmall } from 'lucide-react';
+
+const dotVariants = cva('', {
+ variants: {
+ variant: {
+ neutral: 'fill-neutral-900',
+ active: 'fill-red-600 stroke-red-600',
+ hidden: 'hidden',
+ },
+ },
+ defaultVariants: {
+ variant: 'hidden',
+ },
+});
+
+function NotificationDot({
+ className,
+ dotVariant,
+ ...props
+}: {
+ className: string;
+ dotVariant: VariantProps['variant'];
+}) {
+ return (
+
+ );
+}
+
+export type NDot = VariantProps['variant'];
+export { NotificationDot, dotVariants };
diff --git a/src/components/misc/user-card.tsx b/src/components/misc/user-card.tsx
new file mode 100644
index 0000000..faefc35
--- /dev/null
+++ b/src/components/misc/user-card.tsx
@@ -0,0 +1,29 @@
+import { useGetApiUserMe } from '@/generated/api/user/user';
+import { Avatar } from '@/components/ui/avatar';
+import Image from 'next/image';
+import { User } from 'lucide-react';
+
+export default function UserCard() {
+ const { data } = useGetApiUserMe();
+ return (
+
+
+ {data?.data.user.image ? (
+
+ ) : (
+
+ )}
+
+
{data?.data.user.name}
+
+ {data?.data.user.email}
+
+
+ );
+}
diff --git a/src/components/misc/user-dropdown.tsx b/src/components/misc/user-dropdown.tsx
new file mode 100644
index 0000000..8f5aa05
--- /dev/null
+++ b/src/components/misc/user-dropdown.tsx
@@ -0,0 +1,59 @@
+'use client';
+
+import { Avatar } from '@/components/ui/avatar';
+import { Button } from '@/components/ui/button';
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuItem,
+ // DropdownMenuLabel,
+ // DropdownMenuPortal,
+ DropdownMenuSeparator,
+ // DropdownMenuShortcut,
+ // DropdownMenuSub,
+ // DropdownMenuSubContent,
+ // DropdownMenuSubTrigger,
+ DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu';
+import { useGetApiUserMe } from '@/generated/api/user/user';
+import { ChevronDown, User } from 'lucide-react';
+import Image from 'next/image';
+import Link from 'next/link';
+import UserCard from '@/components/misc/user-card';
+
+export default function UserDropdown() {
+ const { data } = useGetApiUserMe();
+ return (
+
+
+
+
+
+
+
+
+
+ Settings
+
+
+ Logout
+
+
+
+ );
+}
diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx
new file mode 100644
index 0000000..6a21b65
--- /dev/null
+++ b/src/components/ui/avatar.tsx
@@ -0,0 +1,53 @@
+'use client';
+
+import * as React from 'react';
+import * as AvatarPrimitive from '@radix-ui/react-avatar';
+
+import { cn } from '@/lib/utils';
+
+function Avatar({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AvatarImage({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AvatarFallback({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export { Avatar, AvatarImage, AvatarFallback };
diff --git a/src/components/ui/sheet.tsx b/src/components/ui/sheet.tsx
index 84649ad..e8d5ec1 100644
--- a/src/components/ui/sheet.tsx
+++ b/src/components/ui/sheet.tsx
@@ -1,31 +1,31 @@
-"use client"
+'use client';
-import * as React from "react"
-import * as SheetPrimitive from "@radix-ui/react-dialog"
-import { XIcon } from "lucide-react"
+import * as React from 'react';
+import * as SheetPrimitive from '@radix-ui/react-dialog';
+import { XIcon } from 'lucide-react';
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils';
function Sheet({ ...props }: React.ComponentProps) {
- return
+ return ;
}
function SheetTrigger({
...props
}: React.ComponentProps) {
- return
+ return ;
}
function SheetClose({
...props
}: React.ComponentProps) {
- return
+ return ;
}
function SheetPortal({
...props
}: React.ComponentProps) {
- return
+ return ;
}
function SheetOverlay({
@@ -34,71 +34,71 @@ function SheetOverlay({
}: React.ComponentProps) {
return (
- )
+ );
}
function SheetContent({
className,
children,
- side = "right",
+ side = 'right',
...props
}: React.ComponentProps & {
- side?: "top" | "right" | "bottom" | "left"
+ side?: 'top' | 'right' | 'bottom' | 'left';
}) {
return (
{children}
-
-
- Close
+
+
+ Close
- )
+ );
}
-function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
+function SheetHeader({ className, ...props }: React.ComponentProps<'div'>) {
return (
- )
+ );
}
-function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
+function SheetFooter({ className, ...props }: React.ComponentProps<'div'>) {
return (
- )
+ );
}
function SheetTitle({
@@ -107,11 +107,11 @@ function SheetTitle({
}: React.ComponentProps) {
return (
- )
+ );
}
function SheetDescription({
@@ -120,11 +120,11 @@ function SheetDescription({
}: React.ComponentProps) {
return (
- )
+ );
}
export {
@@ -136,4 +136,4 @@ export {
SheetFooter,
SheetTitle,
SheetDescription,
-}
+};
diff --git a/src/components/ui/skeleton.tsx b/src/components/ui/skeleton.tsx
index 32ea0ef..a9344b2 100644
--- a/src/components/ui/skeleton.tsx
+++ b/src/components/ui/skeleton.tsx
@@ -1,13 +1,13 @@
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils';
-function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
+function Skeleton({ className, ...props }: React.ComponentProps<'div'>) {
return (
- )
+ );
}
-export { Skeleton }
+export { Skeleton };
diff --git a/src/components/ui/tooltip.tsx b/src/components/ui/tooltip.tsx
index 4ee26b3..2b8b1d7 100644
--- a/src/components/ui/tooltip.tsx
+++ b/src/components/ui/tooltip.tsx
@@ -1,9 +1,9 @@
-"use client"
+'use client';
-import * as React from "react"
-import * as TooltipPrimitive from "@radix-ui/react-tooltip"
+import * as React from 'react';
+import * as TooltipPrimitive from '@radix-ui/react-tooltip';
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils';
function TooltipProvider({
delayDuration = 0,
@@ -11,11 +11,11 @@ function TooltipProvider({
}: React.ComponentProps) {
return (
- )
+ );
}
function Tooltip({
@@ -23,15 +23,15 @@ function Tooltip({
}: React.ComponentProps) {
return (
-
+
- )
+ );
}
function TooltipTrigger({
...props
}: React.ComponentProps) {
- return
+ return ;
}
function TooltipContent({
@@ -43,19 +43,19 @@ function TooltipContent({
return (
{children}
-
+
- )
+ );
}
-export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
+export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
diff --git a/src/components/query-provider.tsx b/src/components/wrappers/query-provider.tsx
similarity index 100%
rename from src/components/query-provider.tsx
rename to src/components/wrappers/query-provider.tsx
diff --git a/src/components/wrappers/sidebar-provider.tsx b/src/components/wrappers/sidebar-provider.tsx
index 3873fa4..3c9ff95 100644
--- a/src/components/wrappers/sidebar-provider.tsx
+++ b/src/components/wrappers/sidebar-provider.tsx
@@ -1,7 +1,7 @@
'use client';
import React from 'react';
-import { SidebarProvider } from '../ui/sidebar';
+import { SidebarProvider } from '../custom-ui/sidebar';
export default function SidebarProviderWrapper({
defaultOpen,
diff --git a/src/hooks/use-mobile.ts b/src/hooks/use-mobile.ts
index 2b0fe1d..821f8ff 100644
--- a/src/hooks/use-mobile.ts
+++ b/src/hooks/use-mobile.ts
@@ -1,19 +1,21 @@
-import * as React from "react"
+import * as React from 'react';
-const MOBILE_BREAKPOINT = 768
+const MOBILE_BREAKPOINT = 768;
export function useIsMobile() {
- const [isMobile, setIsMobile] = React.useState(undefined)
+ const [isMobile, setIsMobile] = React.useState(
+ undefined,
+ );
React.useEffect(() => {
- const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
const onChange = () => {
- setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
- }
- mql.addEventListener("change", onChange)
- setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
- return () => mql.removeEventListener("change", onChange)
- }, [])
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
+ };
+ mql.addEventListener('change', onChange);
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
+ return () => mql.removeEventListener('change', onChange);
+ }, []);
- return !!isMobile
+ return !!isMobile;
}
diff --git a/yarn.lock b/yarn.lock
index e1ff28c..90dc258 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1299,6 +1299,29 @@ __metadata:
languageName: node
linkType: hard
+"@radix-ui/react-avatar@npm:^1.1.10":
+ version: 1.1.10
+ resolution: "@radix-ui/react-avatar@npm:1.1.10"
+ dependencies:
+ "@radix-ui/react-context": "npm:1.1.2"
+ "@radix-ui/react-primitive": "npm:2.1.3"
+ "@radix-ui/react-use-callback-ref": "npm:1.1.1"
+ "@radix-ui/react-use-is-hydrated": "npm:0.1.0"
+ "@radix-ui/react-use-layout-effect": "npm:1.1.1"
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+ checksum: 10c0/9fb0cf9a9d0fdbeaa2efda476402fc09db2e6ff9cd9aa3ea1d315d9c9579840722a4833725cb196c455e0bd775dfe04221a4f6855685ce89d2133c42e2b07e5f
+ languageName: node
+ linkType: hard
+
"@radix-ui/react-collapsible@npm:^1.1.11":
version: 1.1.11
resolution: "@radix-ui/react-collapsible@npm:1.1.11"
@@ -1441,7 +1464,7 @@ __metadata:
languageName: node
linkType: hard
-"@radix-ui/react-dropdown-menu@npm:^2.1.14":
+"@radix-ui/react-dropdown-menu@npm:^2.1.15":
version: 2.1.15
resolution: "@radix-ui/react-dropdown-menu@npm:2.1.15"
dependencies:
@@ -1951,6 +1974,21 @@ __metadata:
languageName: node
linkType: hard
+"@radix-ui/react-use-is-hydrated@npm:0.1.0":
+ version: 0.1.0
+ resolution: "@radix-ui/react-use-is-hydrated@npm:0.1.0"
+ dependencies:
+ use-sync-external-store: "npm:^1.5.0"
+ peerDependencies:
+ "@types/react": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ checksum: 10c0/635079bafe32829fc7405895154568ea94a22689b170489fd6d77668e4885e72ff71ed6d0ea3d602852841ef0f1927aa400fee2178d5dfbeb8bc9297da7d6498
+ languageName: node
+ linkType: hard
+
"@radix-ui/react-use-layout-effect@npm:1.1.1":
version: 1.1.1
resolution: "@radix-ui/react-use-layout-effect@npm:1.1.1"
@@ -6637,9 +6675,10 @@ __metadata:
"@fortawesome/react-fontawesome": "npm:^0.2.2"
"@hookform/resolvers": "npm:^5.0.1"
"@prisma/client": "npm:^6.9.0"
+ "@radix-ui/react-avatar": "npm:^1.1.10"
"@radix-ui/react-collapsible": "npm:^1.1.11"
"@radix-ui/react-dialog": "npm:^1.1.14"
- "@radix-ui/react-dropdown-menu": "npm:^2.1.14"
+ "@radix-ui/react-dropdown-menu": "npm:^2.1.15"
"@radix-ui/react-hover-card": "npm:^1.1.13"
"@radix-ui/react-label": "npm:^2.1.6"
"@radix-ui/react-scroll-area": "npm:^1.2.8"
@@ -9385,7 +9424,7 @@ __metadata:
languageName: node
linkType: hard
-"use-sync-external-store@npm:^1.4.0":
+"use-sync-external-store@npm:^1.4.0, use-sync-external-store@npm:^1.5.0":
version: 1.5.0
resolution: "use-sync-external-store@npm:1.5.0"
peerDependencies:
From 280fa57e4594ccdef989c5fa22a629925b93e992 Mon Sep 17 00:00:00 2001
From: Dominik Stahl
Date: Mon, 23 Jun 2025 09:51:06 +0200
Subject: [PATCH 04/16] feat(api): implement /api/user/me/password endpoint
add an endpoint to allow the user to change his password
---
src/app/api/user/me/password/route.ts | 119 ++++++++++++++++++++++++
src/app/api/user/me/password/swagger.ts | 43 +++++++++
src/app/api/user/me/validation.ts | 9 ++
3 files changed, 171 insertions(+)
create mode 100644 src/app/api/user/me/password/route.ts
create mode 100644 src/app/api/user/me/password/swagger.ts
diff --git a/src/app/api/user/me/password/route.ts b/src/app/api/user/me/password/route.ts
new file mode 100644
index 0000000..03fb426
--- /dev/null
+++ b/src/app/api/user/me/password/route.ts
@@ -0,0 +1,119 @@
+import { auth } from '@/auth';
+import { prisma } from '@/prisma';
+import { updateUserPasswordServerSchema } from '../validation';
+import {
+ returnZodTypeCheckedResponse,
+ userAuthenticated,
+} from '@/lib/apiHelpers';
+import { FullUserResponseSchema } from '../../validation';
+import {
+ ErrorResponseSchema,
+ ZodErrorResponseSchema,
+} from '@/app/api/validation';
+import bcrypt from 'bcryptjs';
+
+export const PATCH = auth(async function PATCH(req) {
+ const authCheck = userAuthenticated(req);
+ if (!authCheck.continue)
+ return returnZodTypeCheckedResponse(
+ ErrorResponseSchema,
+ authCheck.response,
+ authCheck.metadata,
+ );
+
+ const body = await req.json();
+ const parsedBody = updateUserPasswordServerSchema.safeParse(body);
+ if (!parsedBody.success)
+ return returnZodTypeCheckedResponse(
+ ZodErrorResponseSchema,
+ {
+ success: false,
+ message: 'Invalid request data',
+ errors: parsedBody.error.issues,
+ },
+ { status: 400 },
+ );
+
+ const { current_password, new_password } = parsedBody.data;
+
+ const dbUser = await prisma.user.findUnique({
+ where: {
+ id: authCheck.user.id,
+ },
+ include: {
+ accounts: true,
+ },
+ });
+
+ if (!dbUser)
+ return returnZodTypeCheckedResponse(
+ ErrorResponseSchema,
+ {
+ success: false,
+ message: 'User not found',
+ },
+ { status: 404 },
+ );
+
+ if (!dbUser.password_hash)
+ return returnZodTypeCheckedResponse(
+ ErrorResponseSchema,
+ {
+ success: false,
+ message: 'User does not have a password set',
+ },
+ { status: 400 },
+ );
+
+ if (dbUser.accounts.length === 0 || dbUser.accounts[0].provider !== 'credentials')
+ return returnZodTypeCheckedResponse(
+ ErrorResponseSchema,
+ {
+ success: false,
+ message: 'Credentials login is not enabled for this user',
+ },
+ { status: 400 },
+ );
+
+ const isCurrentPasswordValid = await bcrypt.compare(
+ current_password,
+ dbUser.password_hash || '',
+ );
+
+ if (!isCurrentPasswordValid)
+ return returnZodTypeCheckedResponse(
+ ErrorResponseSchema,
+ {
+ success: false,
+ message: 'Current password is incorrect',
+ },
+ { status: 401 },
+ );
+
+ const hashedNewPassword = await bcrypt.hash(new_password, 10);
+
+ const updatedUser = await prisma.user.update({
+ where: {
+ id: dbUser.id,
+ },
+ data: {
+ password_hash: hashedNewPassword,
+ },
+ select: {
+ id: true,
+ name: true,
+ first_name: true,
+ last_name: true,
+ email: true,
+ image: true,
+ timezone: true,
+ created_at: true,
+ updated_at: true,
+ },
+ });
+
+ return returnZodTypeCheckedResponse(FullUserResponseSchema, {
+ success: true,
+ user: updatedUser,
+ });
+});
diff --git a/src/app/api/user/me/password/swagger.ts b/src/app/api/user/me/password/swagger.ts
new file mode 100644
index 0000000..0bc62f0
--- /dev/null
+++ b/src/app/api/user/me/password/swagger.ts
@@ -0,0 +1,43 @@
+import { OpenAPIRegistry } from '@asteasolutions/zod-to-openapi';
+import { FullUserResponseSchema } from '../../validation';
+import { updateUserPasswordServerSchema } from '../validation';
+import {
+ invalidRequestDataResponse,
+ notAuthenticatedResponse,
+ serverReturnedDataValidationErrorResponse,
+ userNotFoundResponse,
+} from '@/lib/defaultApiResponses';
+
+export default function registerSwaggerPaths(registry: OpenAPIRegistry) {
+ registry.registerPath({
+ method: 'patch',
+ path: '/api/user/me/password',
+ description: 'Update the password of the currently authenticated user',
+ request: {
+ body: {
+ description: 'User password update request body',
+ required: true,
+ content: {
+ 'application/json': {
+ schema: updateUserPasswordServerSchema,
+ },
+ },
+ },
+ },
+ responses: {
+ 200: {
+ description: 'User information updated successfully',
+ content: {
+ 'application/json': {
+ schema: FullUserResponseSchema,
+ },
+ },
+ },
+ ...invalidRequestDataResponse,
+ ...notAuthenticatedResponse,
+ ...userNotFoundResponse,
+ ...serverReturnedDataValidationErrorResponse,
+ },
+ tags: ['User'],
+ });
+}
diff --git a/src/app/api/user/me/validation.ts b/src/app/api/user/me/validation.ts
index 49c6219..7fe04f6 100644
--- a/src/app/api/user/me/validation.ts
+++ b/src/app/api/user/me/validation.ts
@@ -4,6 +4,7 @@ import {
lastNameSchema,
newUserEmailServerSchema,
newUserNameServerSchema,
+ passwordSchema,
} from '@/app/api/user/validation';
// ----------------------------------------
@@ -19,3 +20,11 @@ export const updateUserServerSchema = zod.object({
image: zod.string().optional(),
timezone: zod.string().optional(),
});
+
+export const updateUserPasswordServerSchema = zod.object({
+ current_password: zod.string().min(1, 'Current password is required'),
+ new_password: passwordSchema,
+ confirm_new_password: passwordSchema,
+}).refine((data) => data.new_password === data.confirm_new_password, {
+ message: 'New password and confirm new password must match',
+});
From 16b878a2e957941f1d508d6c37ebeaaf52e38b49 Mon Sep 17 00:00:00 2001
From: Dominik Stahl
Date: Mon, 23 Jun 2025 10:40:28 +0200
Subject: [PATCH 05/16] feat(api): stricter user data api types checking
---
src/app/api/user/me/validation.ts | 5 +-
src/app/api/user/validation.ts | 16 +-
src/lib/timezones.ts | 3713 +++++++++++++++++++++++++++++
3 files changed, 3730 insertions(+), 4 deletions(-)
create mode 100644 src/lib/timezones.ts
diff --git a/src/app/api/user/me/validation.ts b/src/app/api/user/me/validation.ts
index 7fe04f6..2e8ff0a 100644
--- a/src/app/api/user/me/validation.ts
+++ b/src/app/api/user/me/validation.ts
@@ -5,6 +5,7 @@ import {
newUserEmailServerSchema,
newUserNameServerSchema,
passwordSchema,
+ timezoneSchema,
} from '@/app/api/user/validation';
// ----------------------------------------
@@ -17,8 +18,8 @@ export const updateUserServerSchema = zod.object({
first_name: firstNameSchema.optional(),
last_name: lastNameSchema.optional(),
email: newUserEmailServerSchema.optional(),
- image: zod.string().optional(),
- timezone: zod.string().optional(),
+ image: zod.url().optional(),
+ timezone: timezoneSchema.optional(),
});
export const updateUserPasswordServerSchema = zod.object({
diff --git a/src/app/api/user/validation.ts b/src/app/api/user/validation.ts
index 79b1e7e..100470e 100644
--- a/src/app/api/user/validation.ts
+++ b/src/app/api/user/validation.ts
@@ -1,6 +1,7 @@
import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi';
import { prisma } from '@/prisma';
import zod from 'zod/v4';
+import { allTimeZones } from '@/lib/timezones';
extendZodWithOpenApi(zod);
@@ -107,6 +108,17 @@ export const passwordSchema = zod
'Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character',
);
+// ----------------------------------------
+//
+// Timezone Validation
+//
+// ----------------------------------------
+export const timezoneSchema = zod
+ .enum(allTimeZones)
+ .openapi('Timezone', {
+ description: 'Valid timezone from the list of supported timezones',
+ });
+
// ----------------------------------------
//
// User Schema Validation (for API responses)
@@ -119,8 +131,8 @@ export const FullUserSchema = zod
first_name: zod.string().nullish(),
last_name: zod.string().nullish(),
email: zod.email(),
- image: zod.string().nullish(),
- timezone: zod.string(),
+ image: zod.url().nullish(),
+ timezone: zod.string().refine((i) => (allTimeZones as string[]).includes(i)).nullish(),
created_at: zod.date(),
updated_at: zod.date(),
})
diff --git a/src/lib/timezones.ts b/src/lib/timezones.ts
new file mode 100644
index 0000000..e1d681a
--- /dev/null
+++ b/src/lib/timezones.ts
@@ -0,0 +1,3713 @@
+export const timezoneData = {
+ ianaVersion: '2025b',
+ fullVersionId: 'TZDB: 2025b (mapping: $Revision$)',
+ zones: [
+ {
+ id: 'Africa/Abidjan',
+ aliases: [
+ 'Africa/Accra',
+ 'Africa/Bamako',
+ 'Africa/Banjul',
+ 'Africa/Conakry',
+ 'Africa/Dakar',
+ 'Africa/Freetown',
+ 'Africa/Lome',
+ 'Africa/Nouakchott',
+ 'Africa/Ouagadougou',
+ 'Africa/Timbuktu',
+ 'Atlantic/Reykjavik',
+ 'Atlantic/St_Helena',
+ 'Iceland',
+ ],
+ location: {
+ countryCode: 'CI',
+ countryName: 'C\u00F4te d\u0027Ivoire',
+ comment: '',
+ latitude: 5.316666666666666,
+ longitude: -4.033333333333333,
+ },
+ },
+ {
+ id: 'Africa/Algiers',
+ aliases: [],
+ location: {
+ countryCode: 'DZ',
+ countryName: 'Algeria',
+ comment: '',
+ latitude: 36.78333333333333,
+ longitude: 3.05,
+ },
+ },
+ {
+ id: 'Africa/Bissau',
+ aliases: [],
+ location: {
+ countryCode: 'GW',
+ countryName: 'Guinea-Bissau',
+ comment: '',
+ latitude: 11.85,
+ longitude: -15.583333333333334,
+ },
+ },
+ {
+ id: 'Africa/Cairo',
+ aliases: ['Egypt'],
+ location: {
+ countryCode: 'EG',
+ countryName: 'Egypt',
+ comment: '',
+ latitude: 30.05,
+ longitude: 31.25,
+ },
+ },
+ {
+ id: 'Africa/Casablanca',
+ aliases: [],
+ location: {
+ countryCode: 'MA',
+ countryName: 'Morocco',
+ comment: '',
+ latitude: 33.65,
+ longitude: -7.583333333333333,
+ },
+ },
+ {
+ id: 'Africa/Ceuta',
+ aliases: [],
+ location: {
+ countryCode: 'ES',
+ countryName: 'Spain',
+ comment: 'Ceuta, Melilla',
+ latitude: 35.88333333333333,
+ longitude: -5.316666666666666,
+ },
+ },
+ {
+ id: 'Africa/El_Aaiun',
+ aliases: [],
+ location: {
+ countryCode: 'EH',
+ countryName: 'Western Sahara',
+ comment: '',
+ latitude: 27.15,
+ longitude: -13.2,
+ },
+ },
+ {
+ id: 'Africa/Johannesburg',
+ aliases: ['Africa/Maseru', 'Africa/Mbabane'],
+ location: {
+ countryCode: 'ZA',
+ countryName: 'South Africa',
+ comment: '',
+ latitude: -26.25,
+ longitude: 28,
+ },
+ },
+ {
+ id: 'Africa/Juba',
+ aliases: [],
+ location: {
+ countryCode: 'SS',
+ countryName: 'South Sudan',
+ comment: '',
+ latitude: 4.85,
+ longitude: 31.616666666666667,
+ },
+ },
+ {
+ id: 'Africa/Khartoum',
+ aliases: [],
+ location: {
+ countryCode: 'SD',
+ countryName: 'Sudan',
+ comment: '',
+ latitude: 15.6,
+ longitude: 32.53333333333333,
+ },
+ },
+ {
+ id: 'Africa/Lagos',
+ aliases: [
+ 'Africa/Bangui',
+ 'Africa/Brazzaville',
+ 'Africa/Douala',
+ 'Africa/Kinshasa',
+ 'Africa/Libreville',
+ 'Africa/Luanda',
+ 'Africa/Malabo',
+ 'Africa/Niamey',
+ 'Africa/Porto-Novo',
+ ],
+ location: {
+ countryCode: 'NG',
+ countryName: 'Nigeria',
+ comment: '',
+ latitude: 6.45,
+ longitude: 3.4,
+ },
+ },
+ {
+ id: 'Africa/Maputo',
+ aliases: [
+ 'Africa/Blantyre',
+ 'Africa/Bujumbura',
+ 'Africa/Gaborone',
+ 'Africa/Harare',
+ 'Africa/Kigali',
+ 'Africa/Lubumbashi',
+ 'Africa/Lusaka',
+ ],
+ location: {
+ countryCode: 'MZ',
+ countryName: 'Mozambique',
+ comment: '',
+ latitude: -25.966666666666665,
+ longitude: 32.583333333333336,
+ },
+ },
+ {
+ id: 'Africa/Monrovia',
+ aliases: [],
+ location: {
+ countryCode: 'LR',
+ countryName: 'Liberia',
+ comment: '',
+ latitude: 6.3,
+ longitude: -10.783333333333333,
+ },
+ },
+ {
+ id: 'Africa/Nairobi',
+ aliases: [
+ 'Africa/Addis_Ababa',
+ 'Africa/Asmara',
+ 'Africa/Asmera',
+ 'Africa/Dar_es_Salaam',
+ 'Africa/Djibouti',
+ 'Africa/Kampala',
+ 'Africa/Mogadishu',
+ 'Indian/Antananarivo',
+ 'Indian/Comoro',
+ 'Indian/Mayotte',
+ ],
+ location: {
+ countryCode: 'KE',
+ countryName: 'Kenya',
+ comment: '',
+ latitude: -1.2833333333333334,
+ longitude: 36.81666666666667,
+ },
+ },
+ {
+ id: 'Africa/Ndjamena',
+ aliases: [],
+ location: {
+ countryCode: 'TD',
+ countryName: 'Chad',
+ comment: '',
+ latitude: 12.116666666666667,
+ longitude: 15.05,
+ },
+ },
+ {
+ id: 'Africa/Sao_Tome',
+ aliases: [],
+ location: {
+ countryCode: 'ST',
+ countryName: 'Sao Tome \u0026 Principe',
+ comment: '',
+ latitude: 0.3333333333333333,
+ longitude: 6.733333333333333,
+ },
+ },
+ {
+ id: 'Africa/Tripoli',
+ aliases: ['Libya'],
+ location: {
+ countryCode: 'LY',
+ countryName: 'Libya',
+ comment: '',
+ latitude: 32.9,
+ longitude: 13.183333333333334,
+ },
+ },
+ {
+ id: 'Africa/Tunis',
+ aliases: [],
+ location: {
+ countryCode: 'TN',
+ countryName: 'Tunisia',
+ comment: '',
+ latitude: 36.8,
+ longitude: 10.183333333333334,
+ },
+ },
+ {
+ id: 'Africa/Windhoek',
+ aliases: [],
+ location: {
+ countryCode: 'NA',
+ countryName: 'Namibia',
+ comment: '',
+ latitude: -22.566666666666666,
+ longitude: 17.1,
+ },
+ },
+ {
+ id: 'America/Adak',
+ aliases: ['America/Atka', 'US/Aleutian'],
+ location: {
+ countryCode: 'US',
+ countryName: 'United States',
+ comment: 'Alaska - western Aleutians',
+ latitude: 51.88,
+ longitude: -176.65805555555556,
+ },
+ },
+ {
+ id: 'America/Anchorage',
+ aliases: ['US/Alaska'],
+ location: {
+ countryCode: 'US',
+ countryName: 'United States',
+ comment: 'Alaska (most areas)',
+ latitude: 61.21805555555556,
+ longitude: -149.90027777777777,
+ },
+ },
+ {
+ id: 'America/Araguaina',
+ aliases: [],
+ location: {
+ countryCode: 'BR',
+ countryName: 'Brazil',
+ comment: 'Tocantins',
+ latitude: -7.2,
+ longitude: -48.2,
+ },
+ },
+ {
+ id: 'America/Argentina/Buenos_Aires',
+ aliases: ['America/Buenos_Aires'],
+ location: {
+ countryCode: 'AR',
+ countryName: 'Argentina',
+ comment: 'Buenos Aires (BA, CF)',
+ latitude: -34.6,
+ longitude: -58.45,
+ },
+ },
+ {
+ id: 'America/Argentina/Catamarca',
+ aliases: ['America/Argentina/ComodRivadavia', 'America/Catamarca'],
+ location: {
+ countryCode: 'AR',
+ countryName: 'Argentina',
+ comment: 'Catamarca (CT), Chubut (CH)',
+ latitude: -28.466666666666665,
+ longitude: -65.78333333333333,
+ },
+ },
+ {
+ id: 'America/Argentina/Cordoba',
+ aliases: ['America/Cordoba', 'America/Rosario'],
+ location: {
+ countryCode: 'AR',
+ countryName: 'Argentina',
+ comment: 'Argentina (most areas: CB, CC, CN, ER, FM, MN, SE, SF)',
+ latitude: -31.4,
+ longitude: -64.18333333333334,
+ },
+ },
+ {
+ id: 'America/Argentina/Jujuy',
+ aliases: ['America/Jujuy'],
+ location: {
+ countryCode: 'AR',
+ countryName: 'Argentina',
+ comment: 'Jujuy (JY)',
+ latitude: -24.183333333333334,
+ longitude: -65.3,
+ },
+ },
+ {
+ id: 'America/Argentina/La_Rioja',
+ aliases: [],
+ location: {
+ countryCode: 'AR',
+ countryName: 'Argentina',
+ comment: 'La Rioja (LR)',
+ latitude: -29.433333333333334,
+ longitude: -66.85,
+ },
+ },
+ {
+ id: 'America/Argentina/Mendoza',
+ aliases: ['America/Mendoza'],
+ location: {
+ countryCode: 'AR',
+ countryName: 'Argentina',
+ comment: 'Mendoza (MZ)',
+ latitude: -32.88333333333333,
+ longitude: -68.81666666666666,
+ },
+ },
+ {
+ id: 'America/Argentina/Rio_Gallegos',
+ aliases: [],
+ location: {
+ countryCode: 'AR',
+ countryName: 'Argentina',
+ comment: 'Santa Cruz (SC)',
+ latitude: -51.63333333333333,
+ longitude: -69.21666666666667,
+ },
+ },
+ {
+ id: 'America/Argentina/Salta',
+ aliases: [],
+ location: {
+ countryCode: 'AR',
+ countryName: 'Argentina',
+ comment: 'Salta (SA, LP, NQ, RN)',
+ latitude: -24.783333333333335,
+ longitude: -65.41666666666667,
+ },
+ },
+ {
+ id: 'America/Argentina/San_Juan',
+ aliases: [],
+ location: {
+ countryCode: 'AR',
+ countryName: 'Argentina',
+ comment: 'San Juan (SJ)',
+ latitude: -31.533333333333335,
+ longitude: -68.51666666666667,
+ },
+ },
+ {
+ id: 'America/Argentina/San_Luis',
+ aliases: [],
+ location: {
+ countryCode: 'AR',
+ countryName: 'Argentina',
+ comment: 'San Luis (SL)',
+ latitude: -33.31666666666667,
+ longitude: -66.35,
+ },
+ },
+ {
+ id: 'America/Argentina/Tucuman',
+ aliases: [],
+ location: {
+ countryCode: 'AR',
+ countryName: 'Argentina',
+ comment: 'Tucuman (TM)',
+ latitude: -26.816666666666666,
+ longitude: -65.21666666666667,
+ },
+ },
+ {
+ id: 'America/Argentina/Ushuaia',
+ aliases: [],
+ location: {
+ countryCode: 'AR',
+ countryName: 'Argentina',
+ comment: 'Tierra del Fuego (TF)',
+ latitude: -54.8,
+ longitude: -68.3,
+ },
+ },
+ {
+ id: 'America/Asuncion',
+ aliases: [],
+ location: {
+ countryCode: 'PY',
+ countryName: 'Paraguay',
+ comment: '',
+ latitude: -25.266666666666666,
+ longitude: -57.666666666666664,
+ },
+ },
+ {
+ id: 'America/Bahia',
+ aliases: [],
+ location: {
+ countryCode: 'BR',
+ countryName: 'Brazil',
+ comment: 'Bahia',
+ latitude: -12.983333333333333,
+ longitude: -38.516666666666666,
+ },
+ },
+ {
+ id: 'America/Bahia_Banderas',
+ aliases: [],
+ location: {
+ countryCode: 'MX',
+ countryName: 'Mexico',
+ comment: 'Bahia de Banderas',
+ latitude: 20.8,
+ longitude: -105.25,
+ },
+ },
+ {
+ id: 'America/Barbados',
+ aliases: [],
+ location: {
+ countryCode: 'BB',
+ countryName: 'Barbados',
+ comment: '',
+ latitude: 13.1,
+ longitude: -59.61666666666667,
+ },
+ },
+ {
+ id: 'America/Belem',
+ aliases: [],
+ location: {
+ countryCode: 'BR',
+ countryName: 'Brazil',
+ comment: 'Para (east), Amapa',
+ latitude: -1.45,
+ longitude: -48.483333333333334,
+ },
+ },
+ {
+ id: 'America/Belize',
+ aliases: [],
+ location: {
+ countryCode: 'BZ',
+ countryName: 'Belize',
+ comment: '',
+ latitude: 17.5,
+ longitude: -88.2,
+ },
+ },
+ {
+ id: 'America/Boa_Vista',
+ aliases: [],
+ location: {
+ countryCode: 'BR',
+ countryName: 'Brazil',
+ comment: 'Roraima',
+ latitude: 2.816666666666667,
+ longitude: -60.666666666666664,
+ },
+ },
+ {
+ id: 'America/Bogota',
+ aliases: [],
+ location: {
+ countryCode: 'CO',
+ countryName: 'Colombia',
+ comment: '',
+ latitude: 4.6,
+ longitude: -74.08333333333333,
+ },
+ },
+ {
+ id: 'America/Boise',
+ aliases: [],
+ location: {
+ countryCode: 'US',
+ countryName: 'United States',
+ comment: 'Mountain - ID (south), OR (east)',
+ latitude: 43.61361111111111,
+ longitude: -116.2025,
+ },
+ },
+ {
+ id: 'America/Cambridge_Bay',
+ aliases: [],
+ location: {
+ countryCode: 'CA',
+ countryName: 'Canada',
+ comment: 'Mountain - NU (west)',
+ latitude: 69.1138888888889,
+ longitude: -105.05277777777778,
+ },
+ },
+ {
+ id: 'America/Campo_Grande',
+ aliases: [],
+ location: {
+ countryCode: 'BR',
+ countryName: 'Brazil',
+ comment: 'Mato Grosso do Sul',
+ latitude: -20.45,
+ longitude: -54.61666666666667,
+ },
+ },
+ {
+ id: 'America/Cancun',
+ aliases: [],
+ location: {
+ countryCode: 'MX',
+ countryName: 'Mexico',
+ comment: 'Quintana Roo',
+ latitude: 21.083333333333332,
+ longitude: -86.76666666666667,
+ },
+ },
+ {
+ id: 'America/Caracas',
+ aliases: [],
+ location: {
+ countryCode: 'VE',
+ countryName: 'Venezuela',
+ comment: '',
+ latitude: 10.5,
+ longitude: -66.93333333333334,
+ },
+ },
+ {
+ id: 'America/Cayenne',
+ aliases: [],
+ location: {
+ countryCode: 'GF',
+ countryName: 'French Guiana',
+ comment: '',
+ latitude: 4.933333333333334,
+ longitude: -52.333333333333336,
+ },
+ },
+ {
+ id: 'America/Chicago',
+ aliases: ['CST6CDT', 'US/Central'],
+ location: {
+ countryCode: 'US',
+ countryName: 'United States',
+ comment: 'Central (most areas)',
+ latitude: 41.85,
+ longitude: -87.65,
+ },
+ },
+ {
+ id: 'America/Chihuahua',
+ aliases: [],
+ location: {
+ countryCode: 'MX',
+ countryName: 'Mexico',
+ comment: 'Chihuahua (most areas)',
+ latitude: 28.633333333333333,
+ longitude: -106.08333333333333,
+ },
+ },
+ {
+ id: 'America/Ciudad_Juarez',
+ aliases: [],
+ location: {
+ countryCode: 'MX',
+ countryName: 'Mexico',
+ comment: 'Chihuahua (US border - west)',
+ latitude: 31.733333333333334,
+ longitude: -106.48333333333333,
+ },
+ },
+ {
+ id: 'America/Costa_Rica',
+ aliases: [],
+ location: {
+ countryCode: 'CR',
+ countryName: 'Costa Rica',
+ comment: '',
+ latitude: 9.933333333333334,
+ longitude: -84.08333333333333,
+ },
+ },
+ {
+ id: 'America/Coyhaique',
+ aliases: [],
+ location: {
+ countryCode: 'CL',
+ countryName: 'Chile',
+ comment: 'Aysen Region',
+ latitude: -45.56666666666667,
+ longitude: -72.06666666666666,
+ },
+ },
+ {
+ id: 'America/Cuiaba',
+ aliases: [],
+ location: {
+ countryCode: 'BR',
+ countryName: 'Brazil',
+ comment: 'Mato Grosso',
+ latitude: -15.583333333333334,
+ longitude: -56.083333333333336,
+ },
+ },
+ {
+ id: 'America/Danmarkshavn',
+ aliases: [],
+ location: {
+ countryCode: 'GL',
+ countryName: 'Greenland',
+ comment: 'National Park (east coast)',
+ latitude: 76.76666666666667,
+ longitude: -18.666666666666668,
+ },
+ },
+ {
+ id: 'America/Dawson',
+ aliases: [],
+ location: {
+ countryCode: 'CA',
+ countryName: 'Canada',
+ comment: 'MST - Yukon (west)',
+ latitude: 64.06666666666666,
+ longitude: -139.41666666666666,
+ },
+ },
+ {
+ id: 'America/Dawson_Creek',
+ aliases: [],
+ location: {
+ countryCode: 'CA',
+ countryName: 'Canada',
+ comment: 'MST - BC (Dawson Cr, Ft St John)',
+ latitude: 55.766666666666666,
+ longitude: -120.23333333333333,
+ },
+ },
+ {
+ id: 'America/Denver',
+ aliases: ['America/Shiprock', 'MST7MDT', 'Navajo', 'US/Mountain'],
+ location: {
+ countryCode: 'US',
+ countryName: 'United States',
+ comment: 'Mountain (most areas)',
+ latitude: 39.73916666666667,
+ longitude: -104.98416666666667,
+ },
+ },
+ {
+ id: 'America/Detroit',
+ aliases: ['US/Michigan'],
+ location: {
+ countryCode: 'US',
+ countryName: 'United States',
+ comment: 'Eastern - MI (most areas)',
+ latitude: 42.33138888888889,
+ longitude: -83.04583333333333,
+ },
+ },
+ {
+ id: 'America/Edmonton',
+ aliases: ['America/Yellowknife', 'Canada/Mountain'],
+ location: {
+ countryCode: 'CA',
+ countryName: 'Canada',
+ comment: 'Mountain - AB, BC(E), NT(E), SK(W)',
+ latitude: 53.55,
+ longitude: -113.46666666666667,
+ },
+ },
+ {
+ id: 'America/Eirunepe',
+ aliases: [],
+ location: {
+ countryCode: 'BR',
+ countryName: 'Brazil',
+ comment: 'Amazonas (west)',
+ latitude: -6.666666666666667,
+ longitude: -69.86666666666666,
+ },
+ },
+ {
+ id: 'America/El_Salvador',
+ aliases: [],
+ location: {
+ countryCode: 'SV',
+ countryName: 'El Salvador',
+ comment: '',
+ latitude: 13.7,
+ longitude: -89.2,
+ },
+ },
+ {
+ id: 'America/Fort_Nelson',
+ aliases: [],
+ location: {
+ countryCode: 'CA',
+ countryName: 'Canada',
+ comment: 'MST - BC (Ft Nelson)',
+ latitude: 58.8,
+ longitude: -122.7,
+ },
+ },
+ {
+ id: 'America/Fortaleza',
+ aliases: [],
+ location: {
+ countryCode: 'BR',
+ countryName: 'Brazil',
+ comment: 'Brazil (northeast: MA, PI, CE, RN, PB)',
+ latitude: -3.716666666666667,
+ longitude: -38.5,
+ },
+ },
+ {
+ id: 'America/Glace_Bay',
+ aliases: [],
+ location: {
+ countryCode: 'CA',
+ countryName: 'Canada',
+ comment: 'Atlantic - NS (Cape Breton)',
+ latitude: 46.2,
+ longitude: -59.95,
+ },
+ },
+ {
+ id: 'America/Goose_Bay',
+ aliases: [],
+ location: {
+ countryCode: 'CA',
+ countryName: 'Canada',
+ comment: 'Atlantic - Labrador (most areas)',
+ latitude: 53.333333333333336,
+ longitude: -60.416666666666664,
+ },
+ },
+ {
+ id: 'America/Grand_Turk',
+ aliases: [],
+ location: {
+ countryCode: 'TC',
+ countryName: 'Turks \u0026 Caicos Is',
+ comment: '',
+ latitude: 21.466666666666665,
+ longitude: -71.13333333333334,
+ },
+ },
+ {
+ id: 'America/Guatemala',
+ aliases: [],
+ location: {
+ countryCode: 'GT',
+ countryName: 'Guatemala',
+ comment: '',
+ latitude: 14.633333333333333,
+ longitude: -90.51666666666667,
+ },
+ },
+ {
+ id: 'America/Guayaquil',
+ aliases: [],
+ location: {
+ countryCode: 'EC',
+ countryName: 'Ecuador',
+ comment: 'Ecuador (mainland)',
+ latitude: -2.1666666666666665,
+ longitude: -79.83333333333333,
+ },
+ },
+ {
+ id: 'America/Guyana',
+ aliases: [],
+ location: {
+ countryCode: 'GY',
+ countryName: 'Guyana',
+ comment: '',
+ latitude: 6.8,
+ longitude: -58.166666666666664,
+ },
+ },
+ {
+ id: 'America/Halifax',
+ aliases: ['Canada/Atlantic'],
+ location: {
+ countryCode: 'CA',
+ countryName: 'Canada',
+ comment: 'Atlantic - NS (most areas), PE',
+ latitude: 44.65,
+ longitude: -63.6,
+ },
+ },
+ {
+ id: 'America/Havana',
+ aliases: ['Cuba'],
+ location: {
+ countryCode: 'CU',
+ countryName: 'Cuba',
+ comment: '',
+ latitude: 23.133333333333333,
+ longitude: -82.36666666666666,
+ },
+ },
+ {
+ id: 'America/Hermosillo',
+ aliases: [],
+ location: {
+ countryCode: 'MX',
+ countryName: 'Mexico',
+ comment: 'Sonora',
+ latitude: 29.066666666666666,
+ longitude: -110.96666666666667,
+ },
+ },
+ {
+ id: 'America/Indiana/Indianapolis',
+ aliases: [
+ 'America/Fort_Wayne',
+ 'America/Indianapolis',
+ 'US/East-Indiana',
+ ],
+ location: {
+ countryCode: 'US',
+ countryName: 'United States',
+ comment: 'Eastern - IN (most areas)',
+ latitude: 39.76833333333333,
+ longitude: -86.15805555555555,
+ },
+ },
+ {
+ id: 'America/Indiana/Knox',
+ aliases: ['America/Knox_IN', 'US/Indiana-Starke'],
+ location: {
+ countryCode: 'US',
+ countryName: 'United States',
+ comment: 'Central - IN (Starke)',
+ latitude: 41.295833333333334,
+ longitude: -86.625,
+ },
+ },
+ {
+ id: 'America/Indiana/Marengo',
+ aliases: [],
+ location: {
+ countryCode: 'US',
+ countryName: 'United States',
+ comment: 'Eastern - IN (Crawford)',
+ latitude: 38.37555555555556,
+ longitude: -86.34472222222222,
+ },
+ },
+ {
+ id: 'America/Indiana/Petersburg',
+ aliases: [],
+ location: {
+ countryCode: 'US',
+ countryName: 'United States',
+ comment: 'Eastern - IN (Pike)',
+ latitude: 38.49194444444444,
+ longitude: -87.2786111111111,
+ },
+ },
+ {
+ id: 'America/Indiana/Tell_City',
+ aliases: [],
+ location: {
+ countryCode: 'US',
+ countryName: 'United States',
+ comment: 'Central - IN (Perry)',
+ latitude: 37.95305555555556,
+ longitude: -86.76138888888889,
+ },
+ },
+ {
+ id: 'America/Indiana/Vevay',
+ aliases: [],
+ location: {
+ countryCode: 'US',
+ countryName: 'United States',
+ comment: 'Eastern - IN (Switzerland)',
+ latitude: 38.74777777777778,
+ longitude: -85.06722222222223,
+ },
+ },
+ {
+ id: 'America/Indiana/Vincennes',
+ aliases: [],
+ location: {
+ countryCode: 'US',
+ countryName: 'United States',
+ comment: 'Eastern - IN (Da, Du, K, Mn)',
+ latitude: 38.67722222222222,
+ longitude: -87.5286111111111,
+ },
+ },
+ {
+ id: 'America/Indiana/Winamac',
+ aliases: [],
+ location: {
+ countryCode: 'US',
+ countryName: 'United States',
+ comment: 'Eastern - IN (Pulaski)',
+ latitude: 41.05138888888889,
+ longitude: -86.60305555555556,
+ },
+ },
+ {
+ id: 'America/Inuvik',
+ aliases: [],
+ location: {
+ countryCode: 'CA',
+ countryName: 'Canada',
+ comment: 'Mountain - NT (west)',
+ latitude: 68.34972222222223,
+ longitude: -133.71666666666667,
+ },
+ },
+ {
+ id: 'America/Iqaluit',
+ aliases: ['America/Pangnirtung'],
+ location: {
+ countryCode: 'CA',
+ countryName: 'Canada',
+ comment: 'Eastern - NU (most areas)',
+ latitude: 63.733333333333334,
+ longitude: -68.46666666666667,
+ },
+ },
+ {
+ id: 'America/Jamaica',
+ aliases: ['Jamaica'],
+ location: {
+ countryCode: 'JM',
+ countryName: 'Jamaica',
+ comment: '',
+ latitude: 17.968055555555555,
+ longitude: -76.79333333333334,
+ },
+ },
+ {
+ id: 'America/Juneau',
+ aliases: [],
+ location: {
+ countryCode: 'US',
+ countryName: 'United States',
+ comment: 'Alaska - Juneau area',
+ latitude: 58.301944444444445,
+ longitude: -134.41972222222222,
+ },
+ },
+ {
+ id: 'America/Kentucky/Louisville',
+ aliases: ['America/Louisville'],
+ location: {
+ countryCode: 'US',
+ countryName: 'United States',
+ comment: 'Eastern - KY (Louisville area)',
+ latitude: 38.25416666666667,
+ longitude: -85.75944444444444,
+ },
+ },
+ {
+ id: 'America/Kentucky/Monticello',
+ aliases: [],
+ location: {
+ countryCode: 'US',
+ countryName: 'United States',
+ comment: 'Eastern - KY (Wayne)',
+ latitude: 36.82972222222222,
+ longitude: -84.84916666666666,
+ },
+ },
+ {
+ id: 'America/La_Paz',
+ aliases: [],
+ location: {
+ countryCode: 'BO',
+ countryName: 'Bolivia',
+ comment: '',
+ latitude: -16.5,
+ longitude: -68.15,
+ },
+ },
+ {
+ id: 'America/Lima',
+ aliases: [],
+ location: {
+ countryCode: 'PE',
+ countryName: 'Peru',
+ comment: '',
+ latitude: -12.05,
+ longitude: -77.05,
+ },
+ },
+ {
+ id: 'America/Los_Angeles',
+ aliases: ['PST8PDT', 'US/Pacific'],
+ location: {
+ countryCode: 'US',
+ countryName: 'United States',
+ comment: 'Pacific',
+ latitude: 34.05222222222222,
+ longitude: -118.24277777777777,
+ },
+ },
+ {
+ id: 'America/Maceio',
+ aliases: [],
+ location: {
+ countryCode: 'BR',
+ countryName: 'Brazil',
+ comment: 'Alagoas, Sergipe',
+ latitude: -9.666666666666666,
+ longitude: -35.71666666666667,
+ },
+ },
+ {
+ id: 'America/Managua',
+ aliases: [],
+ location: {
+ countryCode: 'NI',
+ countryName: 'Nicaragua',
+ comment: '',
+ latitude: 12.15,
+ longitude: -86.28333333333333,
+ },
+ },
+ {
+ id: 'America/Manaus',
+ aliases: ['Brazil/West'],
+ location: {
+ countryCode: 'BR',
+ countryName: 'Brazil',
+ comment: 'Amazonas (east)',
+ latitude: -3.1333333333333333,
+ longitude: -60.016666666666666,
+ },
+ },
+ {
+ id: 'America/Martinique',
+ aliases: [],
+ location: {
+ countryCode: 'MQ',
+ countryName: 'Martinique',
+ comment: '',
+ latitude: 14.6,
+ longitude: -61.083333333333336,
+ },
+ },
+ {
+ id: 'America/Matamoros',
+ aliases: [],
+ location: {
+ countryCode: 'MX',
+ countryName: 'Mexico',
+ comment: 'Coahuila, Nuevo Leon, Tamaulipas (US border)',
+ latitude: 25.833333333333332,
+ longitude: -97.5,
+ },
+ },
+ {
+ id: 'America/Mazatlan',
+ aliases: ['Mexico/BajaSur'],
+ location: {
+ countryCode: 'MX',
+ countryName: 'Mexico',
+ comment: 'Baja California Sur, Nayarit (most areas), Sinaloa',
+ latitude: 23.216666666666665,
+ longitude: -106.41666666666667,
+ },
+ },
+ {
+ id: 'America/Menominee',
+ aliases: [],
+ location: {
+ countryCode: 'US',
+ countryName: 'United States',
+ comment: 'Central - MI (Wisconsin border)',
+ latitude: 45.10777777777778,
+ longitude: -87.61416666666666,
+ },
+ },
+ {
+ id: 'America/Merida',
+ aliases: [],
+ location: {
+ countryCode: 'MX',
+ countryName: 'Mexico',
+ comment: 'Campeche, Yucatan',
+ latitude: 20.966666666666665,
+ longitude: -89.61666666666666,
+ },
+ },
+ {
+ id: 'America/Metlakatla',
+ aliases: [],
+ location: {
+ countryCode: 'US',
+ countryName: 'United States',
+ comment: 'Alaska - Annette Island',
+ latitude: 55.12694444444445,
+ longitude: -131.57638888888889,
+ },
+ },
+ {
+ id: 'America/Mexico_City',
+ aliases: ['Mexico/General'],
+ location: {
+ countryCode: 'MX',
+ countryName: 'Mexico',
+ comment: 'Central Mexico',
+ latitude: 19.4,
+ longitude: -99.15,
+ },
+ },
+ {
+ id: 'America/Miquelon',
+ aliases: [],
+ location: {
+ countryCode: 'PM',
+ countryName: 'St Pierre \u0026 Miquelon',
+ comment: '',
+ latitude: 47.05,
+ longitude: -56.333333333333336,
+ },
+ },
+ {
+ id: 'America/Moncton',
+ aliases: [],
+ location: {
+ countryCode: 'CA',
+ countryName: 'Canada',
+ comment: 'Atlantic - New Brunswick',
+ latitude: 46.1,
+ longitude: -64.78333333333333,
+ },
+ },
+ {
+ id: 'America/Monterrey',
+ aliases: [],
+ location: {
+ countryCode: 'MX',
+ countryName: 'Mexico',
+ comment: 'Durango; Coahuila, Nuevo Leon, Tamaulipas (most areas)',
+ latitude: 25.666666666666668,
+ longitude: -100.31666666666666,
+ },
+ },
+ {
+ id: 'America/Montevideo',
+ aliases: [],
+ location: {
+ countryCode: 'UY',
+ countryName: 'Uruguay',
+ comment: '',
+ latitude: -34.909166666666664,
+ longitude: -56.2125,
+ },
+ },
+ {
+ id: 'America/New_York',
+ aliases: ['EST5EDT', 'US/Eastern'],
+ location: {
+ countryCode: 'US',
+ countryName: 'United States',
+ comment: 'Eastern (most areas)',
+ latitude: 40.714166666666664,
+ longitude: -74.00638888888889,
+ },
+ },
+ {
+ id: 'America/Nome',
+ aliases: [],
+ location: {
+ countryCode: 'US',
+ countryName: 'United States',
+ comment: 'Alaska (west)',
+ latitude: 64.50111111111111,
+ longitude: -165.4063888888889,
+ },
+ },
+ {
+ id: 'America/Noronha',
+ aliases: ['Brazil/DeNoronha'],
+ location: {
+ countryCode: 'BR',
+ countryName: 'Brazil',
+ comment: 'Atlantic islands',
+ latitude: -3.85,
+ longitude: -32.416666666666664,
+ },
+ },
+ {
+ id: 'America/North_Dakota/Beulah',
+ aliases: [],
+ location: {
+ countryCode: 'US',
+ countryName: 'United States',
+ comment: 'Central - ND (Mercer)',
+ latitude: 47.26416666666667,
+ longitude: -101.77777777777777,
+ },
+ },
+ {
+ id: 'America/North_Dakota/Center',
+ aliases: [],
+ location: {
+ countryCode: 'US',
+ countryName: 'United States',
+ comment: 'Central - ND (Oliver)',
+ latitude: 47.11638888888889,
+ longitude: -101.29916666666666,
+ },
+ },
+ {
+ id: 'America/North_Dakota/New_Salem',
+ aliases: [],
+ location: {
+ countryCode: 'US',
+ countryName: 'United States',
+ comment: 'Central - ND (Morton rural)',
+ latitude: 46.845,
+ longitude: -101.41083333333333,
+ },
+ },
+ {
+ id: 'America/Nuuk',
+ aliases: ['America/Godthab'],
+ location: {
+ countryCode: 'GL',
+ countryName: 'Greenland',
+ comment: 'most of Greenland',
+ latitude: 64.18333333333334,
+ longitude: -51.733333333333334,
+ },
+ },
+ {
+ id: 'America/Ojinaga',
+ aliases: [],
+ location: {
+ countryCode: 'MX',
+ countryName: 'Mexico',
+ comment: 'Chihuahua (US border - east)',
+ latitude: 29.566666666666666,
+ longitude: -104.41666666666667,
+ },
+ },
+ {
+ id: 'America/Panama',
+ aliases: [
+ 'America/Atikokan',
+ 'America/Cayman',
+ 'America/Coral_Harbour',
+ 'EST',
+ ],
+ location: {
+ countryCode: 'PA',
+ countryName: 'Panama',
+ comment: '',
+ latitude: 8.966666666666667,
+ longitude: -79.53333333333333,
+ },
+ },
+ {
+ id: 'America/Paramaribo',
+ aliases: [],
+ location: {
+ countryCode: 'SR',
+ countryName: 'Suriname',
+ comment: '',
+ latitude: 5.833333333333333,
+ longitude: -55.166666666666664,
+ },
+ },
+ {
+ id: 'America/Phoenix',
+ aliases: ['America/Creston', 'MST', 'US/Arizona'],
+ location: {
+ countryCode: 'US',
+ countryName: 'United States',
+ comment: 'MST - AZ (except Navajo)',
+ latitude: 33.44833333333333,
+ longitude: -112.07333333333334,
+ },
+ },
+ {
+ id: 'America/Port-au-Prince',
+ aliases: [],
+ location: {
+ countryCode: 'HT',
+ countryName: 'Haiti',
+ comment: '',
+ latitude: 18.533333333333335,
+ longitude: -72.33333333333333,
+ },
+ },
+ {
+ id: 'America/Porto_Velho',
+ aliases: [],
+ location: {
+ countryCode: 'BR',
+ countryName: 'Brazil',
+ comment: 'Rondonia',
+ latitude: -8.766666666666667,
+ longitude: -63.9,
+ },
+ },
+ {
+ id: 'America/Puerto_Rico',
+ aliases: [
+ 'America/Anguilla',
+ 'America/Antigua',
+ 'America/Aruba',
+ 'America/Blanc-Sablon',
+ 'America/Curacao',
+ 'America/Dominica',
+ 'America/Grenada',
+ 'America/Guadeloupe',
+ 'America/Kralendijk',
+ 'America/Lower_Princes',
+ 'America/Marigot',
+ 'America/Montserrat',
+ 'America/Port_of_Spain',
+ 'America/St_Barthelemy',
+ 'America/St_Kitts',
+ 'America/St_Lucia',
+ 'America/St_Thomas',
+ 'America/St_Vincent',
+ 'America/Tortola',
+ 'America/Virgin',
+ ],
+ location: {
+ countryCode: 'PR',
+ countryName: 'Puerto Rico',
+ comment: '',
+ latitude: 18.468333333333334,
+ longitude: -66.1061111111111,
+ },
+ },
+ {
+ id: 'America/Punta_Arenas',
+ aliases: [],
+ location: {
+ countryCode: 'CL',
+ countryName: 'Chile',
+ comment: 'Magallanes Region',
+ latitude: -53.15,
+ longitude: -70.91666666666667,
+ },
+ },
+ {
+ id: 'America/Rankin_Inlet',
+ aliases: [],
+ location: {
+ countryCode: 'CA',
+ countryName: 'Canada',
+ comment: 'Central - NU (central)',
+ latitude: 62.81666666666667,
+ longitude: -92.08305555555556,
+ },
+ },
+ {
+ id: 'America/Recife',
+ aliases: [],
+ location: {
+ countryCode: 'BR',
+ countryName: 'Brazil',
+ comment: 'Pernambuco',
+ latitude: -8.05,
+ longitude: -34.9,
+ },
+ },
+ {
+ id: 'America/Regina',
+ aliases: ['Canada/Saskatchewan'],
+ location: {
+ countryCode: 'CA',
+ countryName: 'Canada',
+ comment: 'CST - SK (most areas)',
+ latitude: 50.4,
+ longitude: -104.65,
+ },
+ },
+ {
+ id: 'America/Resolute',
+ aliases: [],
+ location: {
+ countryCode: 'CA',
+ countryName: 'Canada',
+ comment: 'Central - NU (Resolute)',
+ latitude: 74.69555555555556,
+ longitude: -94.82916666666667,
+ },
+ },
+ {
+ id: 'America/Rio_Branco',
+ aliases: ['America/Porto_Acre', 'Brazil/Acre'],
+ location: {
+ countryCode: 'BR',
+ countryName: 'Brazil',
+ comment: 'Acre',
+ latitude: -9.966666666666667,
+ longitude: -67.8,
+ },
+ },
+ {
+ id: 'America/Santarem',
+ aliases: [],
+ location: {
+ countryCode: 'BR',
+ countryName: 'Brazil',
+ comment: 'Para (west)',
+ latitude: -2.433333333333333,
+ longitude: -54.86666666666667,
+ },
+ },
+ {
+ id: 'America/Santiago',
+ aliases: ['Chile/Continental'],
+ location: {
+ countryCode: 'CL',
+ countryName: 'Chile',
+ comment: 'most of Chile',
+ latitude: -33.45,
+ longitude: -70.66666666666667,
+ },
+ },
+ {
+ id: 'America/Santo_Domingo',
+ aliases: [],
+ location: {
+ countryCode: 'DO',
+ countryName: 'Dominican Republic',
+ comment: '',
+ latitude: 18.466666666666665,
+ longitude: -69.9,
+ },
+ },
+ {
+ id: 'America/Sao_Paulo',
+ aliases: ['Brazil/East'],
+ location: {
+ countryCode: 'BR',
+ countryName: 'Brazil',
+ comment: 'Brazil (southeast: GO, DF, MG, ES, RJ, SP, PR, SC, RS)',
+ latitude: -23.533333333333335,
+ longitude: -46.61666666666667,
+ },
+ },
+ {
+ id: 'America/Scoresbysund',
+ aliases: [],
+ location: {
+ countryCode: 'GL',
+ countryName: 'Greenland',
+ comment: 'Scoresbysund/Ittoqqortoormiit',
+ latitude: 70.48333333333333,
+ longitude: -21.966666666666665,
+ },
+ },
+ {
+ id: 'America/Sitka',
+ aliases: [],
+ location: {
+ countryCode: 'US',
+ countryName: 'United States',
+ comment: 'Alaska - Sitka area',
+ latitude: 57.17638888888889,
+ longitude: -135.30194444444444,
+ },
+ },
+ {
+ id: 'America/St_Johns',
+ aliases: ['Canada/Newfoundland'],
+ location: {
+ countryCode: 'CA',
+ countryName: 'Canada',
+ comment: 'Newfoundland, Labrador (SE)',
+ latitude: 47.56666666666667,
+ longitude: -52.71666666666667,
+ },
+ },
+ {
+ id: 'America/Swift_Current',
+ aliases: [],
+ location: {
+ countryCode: 'CA',
+ countryName: 'Canada',
+ comment: 'CST - SK (midwest)',
+ latitude: 50.28333333333333,
+ longitude: -107.83333333333333,
+ },
+ },
+ {
+ id: 'America/Tegucigalpa',
+ aliases: [],
+ location: {
+ countryCode: 'HN',
+ countryName: 'Honduras',
+ comment: '',
+ latitude: 14.1,
+ longitude: -87.21666666666667,
+ },
+ },
+ {
+ id: 'America/Thule',
+ aliases: [],
+ location: {
+ countryCode: 'GL',
+ countryName: 'Greenland',
+ comment: 'Thule/Pituffik',
+ latitude: 76.56666666666666,
+ longitude: -68.78333333333333,
+ },
+ },
+ {
+ id: 'America/Tijuana',
+ aliases: ['America/Ensenada', 'America/Santa_Isabel', 'Mexico/BajaNorte'],
+ location: {
+ countryCode: 'MX',
+ countryName: 'Mexico',
+ comment: 'Baja California',
+ latitude: 32.53333333333333,
+ longitude: -117.01666666666667,
+ },
+ },
+ {
+ id: 'America/Toronto',
+ aliases: [
+ 'America/Montreal',
+ 'America/Nassau',
+ 'America/Nipigon',
+ 'America/Thunder_Bay',
+ 'Canada/Eastern',
+ ],
+ location: {
+ countryCode: 'CA',
+ countryName: 'Canada',
+ comment: 'Eastern - ON \u0026 QC (most areas)',
+ latitude: 43.65,
+ longitude: -79.38333333333334,
+ },
+ },
+ {
+ id: 'America/Vancouver',
+ aliases: ['Canada/Pacific'],
+ location: {
+ countryCode: 'CA',
+ countryName: 'Canada',
+ comment: 'Pacific - BC (most areas)',
+ latitude: 49.266666666666666,
+ longitude: -123.11666666666666,
+ },
+ },
+ {
+ id: 'America/Whitehorse',
+ aliases: ['Canada/Yukon'],
+ location: {
+ countryCode: 'CA',
+ countryName: 'Canada',
+ comment: 'MST - Yukon (east)',
+ latitude: 60.71666666666667,
+ longitude: -135.05,
+ },
+ },
+ {
+ id: 'America/Winnipeg',
+ aliases: ['America/Rainy_River', 'Canada/Central'],
+ location: {
+ countryCode: 'CA',
+ countryName: 'Canada',
+ comment: 'Central - ON (west), Manitoba',
+ latitude: 49.88333333333333,
+ longitude: -97.15,
+ },
+ },
+ {
+ id: 'America/Yakutat',
+ aliases: [],
+ location: {
+ countryCode: 'US',
+ countryName: 'United States',
+ comment: 'Alaska - Yakutat',
+ latitude: 59.54694444444444,
+ longitude: -139.72722222222222,
+ },
+ },
+ {
+ id: 'Antarctica/Casey',
+ aliases: [],
+ location: {
+ countryCode: 'AQ',
+ countryName: 'Antarctica',
+ comment: 'Casey',
+ latitude: -66.28333333333333,
+ longitude: 110.51666666666667,
+ },
+ },
+ {
+ id: 'Antarctica/Davis',
+ aliases: [],
+ location: {
+ countryCode: 'AQ',
+ countryName: 'Antarctica',
+ comment: 'Davis',
+ latitude: -68.58333333333333,
+ longitude: 77.96666666666667,
+ },
+ },
+ {
+ id: 'Antarctica/Macquarie',
+ aliases: [],
+ location: {
+ countryCode: 'AU',
+ countryName: 'Australia',
+ comment: 'Macquarie Island',
+ latitude: -54.5,
+ longitude: 158.95,
+ },
+ },
+ {
+ id: 'Antarctica/Mawson',
+ aliases: [],
+ location: {
+ countryCode: 'AQ',
+ countryName: 'Antarctica',
+ comment: 'Mawson',
+ latitude: -67.6,
+ longitude: 62.88333333333333,
+ },
+ },
+ {
+ id: 'Antarctica/Palmer',
+ aliases: [],
+ location: {
+ countryCode: 'AQ',
+ countryName: 'Antarctica',
+ comment: 'Palmer',
+ latitude: -64.8,
+ longitude: -64.1,
+ },
+ },
+ {
+ id: 'Antarctica/Rothera',
+ aliases: [],
+ location: {
+ countryCode: 'AQ',
+ countryName: 'Antarctica',
+ comment: 'Rothera',
+ latitude: -67.56666666666666,
+ longitude: -68.13333333333334,
+ },
+ },
+ {
+ id: 'Antarctica/Troll',
+ aliases: [],
+ location: {
+ countryCode: 'AQ',
+ countryName: 'Antarctica',
+ comment: 'Troll',
+ latitude: -72.01138888888889,
+ longitude: 2.535,
+ },
+ },
+ {
+ id: 'Antarctica/Vostok',
+ aliases: [],
+ location: {
+ countryCode: 'AQ',
+ countryName: 'Antarctica',
+ comment: 'Vostok',
+ latitude: -78.4,
+ longitude: 106.9,
+ },
+ },
+ {
+ id: 'Asia/Almaty',
+ aliases: [],
+ location: {
+ countryCode: 'KZ',
+ countryName: 'Kazakhstan',
+ comment: 'most of Kazakhstan',
+ latitude: 43.25,
+ longitude: 76.95,
+ },
+ },
+ {
+ id: 'Asia/Amman',
+ aliases: [],
+ location: {
+ countryCode: 'JO',
+ countryName: 'Jordan',
+ comment: '',
+ latitude: 31.95,
+ longitude: 35.93333333333333,
+ },
+ },
+ {
+ id: 'Asia/Anadyr',
+ aliases: [],
+ location: {
+ countryCode: 'RU',
+ countryName: 'Russia',
+ comment: 'MSK\u002B09 - Bering Sea',
+ latitude: 64.75,
+ longitude: 177.48333333333332,
+ },
+ },
+ {
+ id: 'Asia/Aqtau',
+ aliases: [],
+ location: {
+ countryCode: 'KZ',
+ countryName: 'Kazakhstan',
+ comment: 'Mangghystau/Mankistau',
+ latitude: 44.516666666666666,
+ longitude: 50.266666666666666,
+ },
+ },
+ {
+ id: 'Asia/Aqtobe',
+ aliases: [],
+ location: {
+ countryCode: 'KZ',
+ countryName: 'Kazakhstan',
+ comment: 'Aqtobe/Aktobe',
+ latitude: 50.28333333333333,
+ longitude: 57.166666666666664,
+ },
+ },
+ {
+ id: 'Asia/Ashgabat',
+ aliases: ['Asia/Ashkhabad'],
+ location: {
+ countryCode: 'TM',
+ countryName: 'Turkmenistan',
+ comment: '',
+ latitude: 37.95,
+ longitude: 58.38333333333333,
+ },
+ },
+ {
+ id: 'Asia/Atyrau',
+ aliases: [],
+ location: {
+ countryCode: 'KZ',
+ countryName: 'Kazakhstan',
+ comment: 'Atyrau/Atirau/Gur\u0027yev',
+ latitude: 47.11666666666667,
+ longitude: 51.93333333333333,
+ },
+ },
+ {
+ id: 'Asia/Baghdad',
+ aliases: [],
+ location: {
+ countryCode: 'IQ',
+ countryName: 'Iraq',
+ comment: '',
+ latitude: 33.35,
+ longitude: 44.416666666666664,
+ },
+ },
+ {
+ id: 'Asia/Baku',
+ aliases: [],
+ location: {
+ countryCode: 'AZ',
+ countryName: 'Azerbaijan',
+ comment: '',
+ latitude: 40.38333333333333,
+ longitude: 49.85,
+ },
+ },
+ {
+ id: 'Asia/Bangkok',
+ aliases: ['Asia/Phnom_Penh', 'Asia/Vientiane', 'Indian/Christmas'],
+ location: {
+ countryCode: 'TH',
+ countryName: 'Thailand',
+ comment: '',
+ latitude: 13.75,
+ longitude: 100.51666666666667,
+ },
+ },
+ {
+ id: 'Asia/Barnaul',
+ aliases: [],
+ location: {
+ countryCode: 'RU',
+ countryName: 'Russia',
+ comment: 'MSK\u002B04 - Altai',
+ latitude: 53.36666666666667,
+ longitude: 83.75,
+ },
+ },
+ {
+ id: 'Asia/Beirut',
+ aliases: [],
+ location: {
+ countryCode: 'LB',
+ countryName: 'Lebanon',
+ comment: '',
+ latitude: 33.88333333333333,
+ longitude: 35.5,
+ },
+ },
+ {
+ id: 'Asia/Bishkek',
+ aliases: [],
+ location: {
+ countryCode: 'KG',
+ countryName: 'Kyrgyzstan',
+ comment: '',
+ latitude: 42.9,
+ longitude: 74.6,
+ },
+ },
+ {
+ id: 'Asia/Chita',
+ aliases: [],
+ location: {
+ countryCode: 'RU',
+ countryName: 'Russia',
+ comment: 'MSK\u002B06 - Zabaykalsky',
+ latitude: 52.05,
+ longitude: 113.46666666666667,
+ },
+ },
+ {
+ id: 'Asia/Colombo',
+ aliases: [],
+ location: {
+ countryCode: 'LK',
+ countryName: 'Sri Lanka',
+ comment: '',
+ latitude: 6.933333333333334,
+ longitude: 79.85,
+ },
+ },
+ {
+ id: 'Asia/Damascus',
+ aliases: [],
+ location: {
+ countryCode: 'SY',
+ countryName: 'Syria',
+ comment: '',
+ latitude: 33.5,
+ longitude: 36.3,
+ },
+ },
+ {
+ id: 'Asia/Dhaka',
+ aliases: ['Asia/Dacca'],
+ location: {
+ countryCode: 'BD',
+ countryName: 'Bangladesh',
+ comment: '',
+ latitude: 23.716666666666665,
+ longitude: 90.41666666666667,
+ },
+ },
+ {
+ id: 'Asia/Dili',
+ aliases: [],
+ location: {
+ countryCode: 'TL',
+ countryName: 'East Timor',
+ comment: '',
+ latitude: -8.55,
+ longitude: 125.58333333333333,
+ },
+ },
+ {
+ id: 'Asia/Dubai',
+ aliases: ['Asia/Muscat', 'Indian/Mahe', 'Indian/Reunion'],
+ location: {
+ countryCode: 'AE',
+ countryName: 'United Arab Emirates',
+ comment: '',
+ latitude: 25.3,
+ longitude: 55.3,
+ },
+ },
+ {
+ id: 'Asia/Dushanbe',
+ aliases: [],
+ location: {
+ countryCode: 'TJ',
+ countryName: 'Tajikistan',
+ comment: '',
+ latitude: 38.583333333333336,
+ longitude: 68.8,
+ },
+ },
+ {
+ id: 'Asia/Famagusta',
+ aliases: [],
+ location: {
+ countryCode: 'CY',
+ countryName: 'Cyprus',
+ comment: 'Northern Cyprus',
+ latitude: 35.11666666666667,
+ longitude: 33.95,
+ },
+ },
+ {
+ id: 'Asia/Gaza',
+ aliases: [],
+ location: {
+ countryCode: 'PS',
+ countryName: 'Palestine',
+ comment: 'Gaza Strip',
+ latitude: 31.5,
+ longitude: 34.46666666666667,
+ },
+ },
+ {
+ id: 'Asia/Hebron',
+ aliases: [],
+ location: {
+ countryCode: 'PS',
+ countryName: 'Palestine',
+ comment: 'West Bank',
+ latitude: 31.533333333333335,
+ longitude: 35.095,
+ },
+ },
+ {
+ id: 'Asia/Ho_Chi_Minh',
+ aliases: ['Asia/Saigon'],
+ location: {
+ countryCode: 'VN',
+ countryName: 'Vietnam',
+ comment: '',
+ latitude: 10.75,
+ longitude: 106.66666666666667,
+ },
+ },
+ {
+ id: 'Asia/Hong_Kong',
+ aliases: ['Hongkong'],
+ location: {
+ countryCode: 'HK',
+ countryName: 'Hong Kong',
+ comment: '',
+ latitude: 22.283333333333335,
+ longitude: 114.15,
+ },
+ },
+ {
+ id: 'Asia/Hovd',
+ aliases: [],
+ location: {
+ countryCode: 'MN',
+ countryName: 'Mongolia',
+ comment: 'Bayan-Olgii, Hovd, Uvs',
+ latitude: 48.016666666666666,
+ longitude: 91.65,
+ },
+ },
+ {
+ id: 'Asia/Irkutsk',
+ aliases: [],
+ location: {
+ countryCode: 'RU',
+ countryName: 'Russia',
+ comment: 'MSK\u002B05 - Irkutsk, Buryatia',
+ latitude: 52.266666666666666,
+ longitude: 104.33333333333333,
+ },
+ },
+ {
+ id: 'Asia/Jakarta',
+ aliases: [],
+ location: {
+ countryCode: 'ID',
+ countryName: 'Indonesia',
+ comment: 'Java, Sumatra',
+ latitude: -6.166666666666667,
+ longitude: 106.8,
+ },
+ },
+ {
+ id: 'Asia/Jayapura',
+ aliases: [],
+ location: {
+ countryCode: 'ID',
+ countryName: 'Indonesia',
+ comment: 'New Guinea (West Papua / Irian Jaya), Malukus/Moluccas',
+ latitude: -2.533333333333333,
+ longitude: 140.7,
+ },
+ },
+ {
+ id: 'Asia/Jerusalem',
+ aliases: ['Asia/Tel_Aviv', 'Israel'],
+ location: {
+ countryCode: 'IL',
+ countryName: 'Israel',
+ comment: '',
+ latitude: 31.780555555555555,
+ longitude: 35.22388888888889,
+ },
+ },
+ {
+ id: 'Asia/Kabul',
+ aliases: [],
+ location: {
+ countryCode: 'AF',
+ countryName: 'Afghanistan',
+ comment: '',
+ latitude: 34.516666666666666,
+ longitude: 69.2,
+ },
+ },
+ {
+ id: 'Asia/Kamchatka',
+ aliases: [],
+ location: {
+ countryCode: 'RU',
+ countryName: 'Russia',
+ comment: 'MSK\u002B09 - Kamchatka',
+ latitude: 53.016666666666666,
+ longitude: 158.65,
+ },
+ },
+ {
+ id: 'Asia/Karachi',
+ aliases: [],
+ location: {
+ countryCode: 'PK',
+ countryName: 'Pakistan',
+ comment: '',
+ latitude: 24.866666666666667,
+ longitude: 67.05,
+ },
+ },
+ {
+ id: 'Asia/Kathmandu',
+ aliases: ['Asia/Katmandu'],
+ location: {
+ countryCode: 'NP',
+ countryName: 'Nepal',
+ comment: '',
+ latitude: 27.716666666666665,
+ longitude: 85.31666666666666,
+ },
+ },
+ {
+ id: 'Asia/Khandyga',
+ aliases: [],
+ location: {
+ countryCode: 'RU',
+ countryName: 'Russia',
+ comment: 'MSK\u002B06 - Tomponsky, Ust-Maysky',
+ latitude: 62.65638888888889,
+ longitude: 135.55388888888888,
+ },
+ },
+ {
+ id: 'Asia/Kolkata',
+ aliases: ['Asia/Calcutta'],
+ location: {
+ countryCode: 'IN',
+ countryName: 'India',
+ comment: '',
+ latitude: 22.533333333333335,
+ longitude: 88.36666666666666,
+ },
+ },
+ {
+ id: 'Asia/Krasnoyarsk',
+ aliases: [],
+ location: {
+ countryCode: 'RU',
+ countryName: 'Russia',
+ comment: 'MSK\u002B04 - Krasnoyarsk area',
+ latitude: 56.016666666666666,
+ longitude: 92.83333333333333,
+ },
+ },
+ {
+ id: 'Asia/Kuching',
+ aliases: ['Asia/Brunei'],
+ location: {
+ countryCode: 'MY',
+ countryName: 'Malaysia',
+ comment: 'Sabah, Sarawak',
+ latitude: 1.55,
+ longitude: 110.33333333333333,
+ },
+ },
+ {
+ id: 'Asia/Macau',
+ aliases: ['Asia/Macao'],
+ location: {
+ countryCode: 'MO',
+ countryName: 'Macau',
+ comment: '',
+ latitude: 22.197222222222223,
+ longitude: 113.54166666666667,
+ },
+ },
+ {
+ id: 'Asia/Magadan',
+ aliases: [],
+ location: {
+ countryCode: 'RU',
+ countryName: 'Russia',
+ comment: 'MSK\u002B08 - Magadan',
+ latitude: 59.56666666666667,
+ longitude: 150.8,
+ },
+ },
+ {
+ id: 'Asia/Makassar',
+ aliases: ['Asia/Ujung_Pandang'],
+ location: {
+ countryCode: 'ID',
+ countryName: 'Indonesia',
+ comment:
+ 'Borneo (east, south), Sulawesi/Celebes, Bali, Nusa Tengarra, Timor (west)',
+ latitude: -5.116666666666666,
+ longitude: 119.4,
+ },
+ },
+ {
+ id: 'Asia/Manila',
+ aliases: [],
+ location: {
+ countryCode: 'PH',
+ countryName: 'Philippines',
+ comment: '',
+ latitude: 14.586666666666666,
+ longitude: 120.96777777777778,
+ },
+ },
+ {
+ id: 'Asia/Nicosia',
+ aliases: ['Europe/Nicosia'],
+ location: {
+ countryCode: 'CY',
+ countryName: 'Cyprus',
+ comment: 'most of Cyprus',
+ latitude: 35.166666666666664,
+ longitude: 33.36666666666667,
+ },
+ },
+ {
+ id: 'Asia/Novokuznetsk',
+ aliases: [],
+ location: {
+ countryCode: 'RU',
+ countryName: 'Russia',
+ comment: 'MSK\u002B04 - Kemerovo',
+ latitude: 53.75,
+ longitude: 87.11666666666666,
+ },
+ },
+ {
+ id: 'Asia/Novosibirsk',
+ aliases: [],
+ location: {
+ countryCode: 'RU',
+ countryName: 'Russia',
+ comment: 'MSK\u002B04 - Novosibirsk',
+ latitude: 55.03333333333333,
+ longitude: 82.91666666666667,
+ },
+ },
+ {
+ id: 'Asia/Omsk',
+ aliases: [],
+ location: {
+ countryCode: 'RU',
+ countryName: 'Russia',
+ comment: 'MSK\u002B03 - Omsk',
+ latitude: 55,
+ longitude: 73.4,
+ },
+ },
+ {
+ id: 'Asia/Oral',
+ aliases: [],
+ location: {
+ countryCode: 'KZ',
+ countryName: 'Kazakhstan',
+ comment: 'West Kazakhstan',
+ latitude: 51.21666666666667,
+ longitude: 51.35,
+ },
+ },
+ {
+ id: 'Asia/Pontianak',
+ aliases: [],
+ location: {
+ countryCode: 'ID',
+ countryName: 'Indonesia',
+ comment: 'Borneo (west, central)',
+ latitude: -0.03333333333333333,
+ longitude: 109.33333333333333,
+ },
+ },
+ {
+ id: 'Asia/Pyongyang',
+ aliases: [],
+ location: {
+ countryCode: 'KP',
+ countryName: 'Korea (North)',
+ comment: '',
+ latitude: 39.016666666666666,
+ longitude: 125.75,
+ },
+ },
+ {
+ id: 'Asia/Qatar',
+ aliases: ['Asia/Bahrain'],
+ location: {
+ countryCode: 'QA',
+ countryName: 'Qatar',
+ comment: '',
+ latitude: 25.283333333333335,
+ longitude: 51.53333333333333,
+ },
+ },
+ {
+ id: 'Asia/Qostanay',
+ aliases: [],
+ location: {
+ countryCode: 'KZ',
+ countryName: 'Kazakhstan',
+ comment: 'Qostanay/Kostanay/Kustanay',
+ latitude: 53.2,
+ longitude: 63.61666666666667,
+ },
+ },
+ {
+ id: 'Asia/Qyzylorda',
+ aliases: [],
+ location: {
+ countryCode: 'KZ',
+ countryName: 'Kazakhstan',
+ comment: 'Qyzylorda/Kyzylorda/Kzyl-Orda',
+ latitude: 44.8,
+ longitude: 65.46666666666667,
+ },
+ },
+ {
+ id: 'Asia/Riyadh',
+ aliases: ['Antarctica/Syowa', 'Asia/Aden', 'Asia/Kuwait'],
+ location: {
+ countryCode: 'SA',
+ countryName: 'Saudi Arabia',
+ comment: '',
+ latitude: 24.633333333333333,
+ longitude: 46.71666666666667,
+ },
+ },
+ {
+ id: 'Asia/Sakhalin',
+ aliases: [],
+ location: {
+ countryCode: 'RU',
+ countryName: 'Russia',
+ comment: 'MSK\u002B08 - Sakhalin Island',
+ latitude: 46.96666666666667,
+ longitude: 142.7,
+ },
+ },
+ {
+ id: 'Asia/Samarkand',
+ aliases: [],
+ location: {
+ countryCode: 'UZ',
+ countryName: 'Uzbekistan',
+ comment: 'Uzbekistan (west)',
+ latitude: 39.666666666666664,
+ longitude: 66.8,
+ },
+ },
+ {
+ id: 'Asia/Seoul',
+ aliases: ['ROK'],
+ location: {
+ countryCode: 'KR',
+ countryName: 'Korea (South)',
+ comment: '',
+ latitude: 37.55,
+ longitude: 126.96666666666667,
+ },
+ },
+ {
+ id: 'Asia/Shanghai',
+ aliases: ['Asia/Chongqing', 'Asia/Chungking', 'Asia/Harbin', 'PRC'],
+ location: {
+ countryCode: 'CN',
+ countryName: 'China',
+ comment: 'Beijing Time',
+ latitude: 31.233333333333334,
+ longitude: 121.46666666666667,
+ },
+ },
+ {
+ id: 'Asia/Singapore',
+ aliases: ['Asia/Kuala_Lumpur', 'Singapore'],
+ location: {
+ countryCode: 'SG',
+ countryName: 'Singapore',
+ comment: '',
+ latitude: 1.2833333333333334,
+ longitude: 103.85,
+ },
+ },
+ {
+ id: 'Asia/Srednekolymsk',
+ aliases: [],
+ location: {
+ countryCode: 'RU',
+ countryName: 'Russia',
+ comment: 'MSK\u002B08 - Sakha (E), N Kuril Is',
+ latitude: 67.46666666666667,
+ longitude: 153.71666666666667,
+ },
+ },
+ {
+ id: 'Asia/Taipei',
+ aliases: ['ROC'],
+ location: {
+ countryCode: 'TW',
+ countryName: 'Taiwan',
+ comment: '',
+ latitude: 25.05,
+ longitude: 121.5,
+ },
+ },
+ {
+ id: 'Asia/Tashkent',
+ aliases: [],
+ location: {
+ countryCode: 'UZ',
+ countryName: 'Uzbekistan',
+ comment: 'Uzbekistan (east)',
+ latitude: 41.333333333333336,
+ longitude: 69.3,
+ },
+ },
+ {
+ id: 'Asia/Tbilisi',
+ aliases: [],
+ location: {
+ countryCode: 'GE',
+ countryName: 'Georgia',
+ comment: '',
+ latitude: 41.71666666666667,
+ longitude: 44.81666666666667,
+ },
+ },
+ {
+ id: 'Asia/Tehran',
+ aliases: ['Iran'],
+ location: {
+ countryCode: 'IR',
+ countryName: 'Iran',
+ comment: '',
+ latitude: 35.666666666666664,
+ longitude: 51.43333333333333,
+ },
+ },
+ {
+ id: 'Asia/Thimphu',
+ aliases: ['Asia/Thimbu'],
+ location: {
+ countryCode: 'BT',
+ countryName: 'Bhutan',
+ comment: '',
+ latitude: 27.466666666666665,
+ longitude: 89.65,
+ },
+ },
+ {
+ id: 'Asia/Tokyo',
+ aliases: ['Japan'],
+ location: {
+ countryCode: 'JP',
+ countryName: 'Japan',
+ comment: '',
+ latitude: 35.654444444444444,
+ longitude: 139.7447222222222,
+ },
+ },
+ {
+ id: 'Asia/Tomsk',
+ aliases: [],
+ location: {
+ countryCode: 'RU',
+ countryName: 'Russia',
+ comment: 'MSK\u002B04 - Tomsk',
+ latitude: 56.5,
+ longitude: 84.96666666666667,
+ },
+ },
+ {
+ id: 'Asia/Ulaanbaatar',
+ aliases: ['Asia/Choibalsan', 'Asia/Ulan_Bator'],
+ location: {
+ countryCode: 'MN',
+ countryName: 'Mongolia',
+ comment: 'most of Mongolia',
+ latitude: 47.916666666666664,
+ longitude: 106.88333333333334,
+ },
+ },
+ {
+ id: 'Asia/Urumqi',
+ aliases: ['Asia/Kashgar'],
+ location: {
+ countryCode: 'CN',
+ countryName: 'China',
+ comment: 'Xinjiang Time',
+ latitude: 43.8,
+ longitude: 87.58333333333333,
+ },
+ },
+ {
+ id: 'Asia/Ust-Nera',
+ aliases: [],
+ location: {
+ countryCode: 'RU',
+ countryName: 'Russia',
+ comment: 'MSK\u002B07 - Oymyakonsky',
+ latitude: 64.56027777777778,
+ longitude: 143.22666666666666,
+ },
+ },
+ {
+ id: 'Asia/Vladivostok',
+ aliases: [],
+ location: {
+ countryCode: 'RU',
+ countryName: 'Russia',
+ comment: 'MSK\u002B07 - Amur River',
+ latitude: 43.166666666666664,
+ longitude: 131.93333333333334,
+ },
+ },
+ {
+ id: 'Asia/Yakutsk',
+ aliases: [],
+ location: {
+ countryCode: 'RU',
+ countryName: 'Russia',
+ comment: 'MSK\u002B06 - Lena River',
+ latitude: 62,
+ longitude: 129.66666666666666,
+ },
+ },
+ {
+ id: 'Asia/Yangon',
+ aliases: ['Asia/Rangoon', 'Indian/Cocos'],
+ location: {
+ countryCode: 'MM',
+ countryName: 'Myanmar (Burma)',
+ comment: '',
+ latitude: 16.783333333333335,
+ longitude: 96.16666666666667,
+ },
+ },
+ {
+ id: 'Asia/Yekaterinburg',
+ aliases: [],
+ location: {
+ countryCode: 'RU',
+ countryName: 'Russia',
+ comment: 'MSK\u002B02 - Urals',
+ latitude: 56.85,
+ longitude: 60.6,
+ },
+ },
+ {
+ id: 'Asia/Yerevan',
+ aliases: [],
+ location: {
+ countryCode: 'AM',
+ countryName: 'Armenia',
+ comment: '',
+ latitude: 40.18333333333333,
+ longitude: 44.5,
+ },
+ },
+ {
+ id: 'Atlantic/Azores',
+ aliases: [],
+ location: {
+ countryCode: 'PT',
+ countryName: 'Portugal',
+ comment: 'Azores',
+ latitude: 37.733333333333334,
+ longitude: -25.666666666666668,
+ },
+ },
+ {
+ id: 'Atlantic/Bermuda',
+ aliases: [],
+ location: {
+ countryCode: 'BM',
+ countryName: 'Bermuda',
+ comment: '',
+ latitude: 32.28333333333333,
+ longitude: -64.76666666666667,
+ },
+ },
+ {
+ id: 'Atlantic/Canary',
+ aliases: [],
+ location: {
+ countryCode: 'ES',
+ countryName: 'Spain',
+ comment: 'Canary Islands',
+ latitude: 28.1,
+ longitude: -15.4,
+ },
+ },
+ {
+ id: 'Atlantic/Cape_Verde',
+ aliases: [],
+ location: {
+ countryCode: 'CV',
+ countryName: 'Cape Verde',
+ comment: '',
+ latitude: 14.916666666666666,
+ longitude: -23.516666666666666,
+ },
+ },
+ {
+ id: 'Atlantic/Faroe',
+ aliases: ['Atlantic/Faeroe'],
+ location: {
+ countryCode: 'FO',
+ countryName: 'Faroe Islands',
+ comment: '',
+ latitude: 62.016666666666666,
+ longitude: -6.766666666666667,
+ },
+ },
+ {
+ id: 'Atlantic/Madeira',
+ aliases: [],
+ location: {
+ countryCode: 'PT',
+ countryName: 'Portugal',
+ comment: 'Madeira Islands',
+ latitude: 32.63333333333333,
+ longitude: -16.9,
+ },
+ },
+ {
+ id: 'Atlantic/South_Georgia',
+ aliases: [],
+ location: {
+ countryCode: 'GS',
+ countryName: 'South Georgia \u0026 the South Sandwich Islands',
+ comment: '',
+ latitude: -54.266666666666666,
+ longitude: -36.53333333333333,
+ },
+ },
+ {
+ id: 'Atlantic/Stanley',
+ aliases: [],
+ location: {
+ countryCode: 'FK',
+ countryName: 'Falkland Islands',
+ comment: '',
+ latitude: -51.7,
+ longitude: -57.85,
+ },
+ },
+ {
+ id: 'Australia/Adelaide',
+ aliases: ['Australia/South'],
+ location: {
+ countryCode: 'AU',
+ countryName: 'Australia',
+ comment: 'South Australia',
+ latitude: -34.916666666666664,
+ longitude: 138.58333333333334,
+ },
+ },
+ {
+ id: 'Australia/Brisbane',
+ aliases: ['Australia/Queensland'],
+ location: {
+ countryCode: 'AU',
+ countryName: 'Australia',
+ comment: 'Queensland (most areas)',
+ latitude: -27.466666666666665,
+ longitude: 153.03333333333333,
+ },
+ },
+ {
+ id: 'Australia/Broken_Hill',
+ aliases: ['Australia/Yancowinna'],
+ location: {
+ countryCode: 'AU',
+ countryName: 'Australia',
+ comment: 'New South Wales (Yancowinna)',
+ latitude: -31.95,
+ longitude: 141.45,
+ },
+ },
+ {
+ id: 'Australia/Darwin',
+ aliases: ['Australia/North'],
+ location: {
+ countryCode: 'AU',
+ countryName: 'Australia',
+ comment: 'Northern Territory',
+ latitude: -12.466666666666667,
+ longitude: 130.83333333333334,
+ },
+ },
+ {
+ id: 'Australia/Eucla',
+ aliases: [],
+ location: {
+ countryCode: 'AU',
+ countryName: 'Australia',
+ comment: 'Western Australia (Eucla)',
+ latitude: -31.716666666666665,
+ longitude: 128.86666666666667,
+ },
+ },
+ {
+ id: 'Australia/Hobart',
+ aliases: ['Australia/Currie', 'Australia/Tasmania'],
+ location: {
+ countryCode: 'AU',
+ countryName: 'Australia',
+ comment: 'Tasmania',
+ latitude: -42.88333333333333,
+ longitude: 147.31666666666666,
+ },
+ },
+ {
+ id: 'Australia/Lindeman',
+ aliases: [],
+ location: {
+ countryCode: 'AU',
+ countryName: 'Australia',
+ comment: 'Queensland (Whitsunday Islands)',
+ latitude: -20.266666666666666,
+ longitude: 149,
+ },
+ },
+ {
+ id: 'Australia/Lord_Howe',
+ aliases: ['Australia/LHI'],
+ location: {
+ countryCode: 'AU',
+ countryName: 'Australia',
+ comment: 'Lord Howe Island',
+ latitude: -31.55,
+ longitude: 159.08333333333334,
+ },
+ },
+ {
+ id: 'Australia/Melbourne',
+ aliases: ['Australia/Victoria'],
+ location: {
+ countryCode: 'AU',
+ countryName: 'Australia',
+ comment: 'Victoria',
+ latitude: -37.81666666666667,
+ longitude: 144.96666666666667,
+ },
+ },
+ {
+ id: 'Australia/Perth',
+ aliases: ['Australia/West'],
+ location: {
+ countryCode: 'AU',
+ countryName: 'Australia',
+ comment: 'Western Australia (most areas)',
+ latitude: -31.95,
+ longitude: 115.85,
+ },
+ },
+ {
+ id: 'Australia/Sydney',
+ aliases: ['Australia/ACT', 'Australia/Canberra', 'Australia/NSW'],
+ location: {
+ countryCode: 'AU',
+ countryName: 'Australia',
+ comment: 'New South Wales (most areas)',
+ latitude: -33.86666666666667,
+ longitude: 151.21666666666667,
+ },
+ },
+ {
+ id: 'Etc/GMT',
+ aliases: [
+ 'Etc/GMT\u002B0',
+ 'Etc/GMT-0',
+ 'Etc/GMT0',
+ 'Etc/Greenwich',
+ 'GMT',
+ 'GMT\u002B0',
+ 'GMT-0',
+ 'GMT0',
+ 'Greenwich',
+ ],
+ location: null,
+ },
+ {
+ id: 'Etc/GMT-1',
+ aliases: [],
+ location: null,
+ },
+ {
+ id: 'Etc/GMT-10',
+ aliases: [],
+ location: null,
+ },
+ {
+ id: 'Etc/GMT-11',
+ aliases: [],
+ location: null,
+ },
+ {
+ id: 'Etc/GMT-12',
+ aliases: [],
+ location: null,
+ },
+ {
+ id: 'Etc/GMT-13',
+ aliases: [],
+ location: null,
+ },
+ {
+ id: 'Etc/GMT-14',
+ aliases: [],
+ location: null,
+ },
+ {
+ id: 'Etc/GMT-2',
+ aliases: [],
+ location: null,
+ },
+ {
+ id: 'Etc/GMT-3',
+ aliases: [],
+ location: null,
+ },
+ {
+ id: 'Etc/GMT-4',
+ aliases: [],
+ location: null,
+ },
+ {
+ id: 'Etc/GMT-5',
+ aliases: [],
+ location: null,
+ },
+ {
+ id: 'Etc/GMT-6',
+ aliases: [],
+ location: null,
+ },
+ {
+ id: 'Etc/GMT-7',
+ aliases: [],
+ location: null,
+ },
+ {
+ id: 'Etc/GMT-8',
+ aliases: [],
+ location: null,
+ },
+ {
+ id: 'Etc/GMT-9',
+ aliases: [],
+ location: null,
+ },
+ {
+ id: 'Etc/GMT\u002B1',
+ aliases: [],
+ location: null,
+ },
+ {
+ id: 'Etc/GMT\u002B10',
+ aliases: [],
+ location: null,
+ },
+ {
+ id: 'Etc/GMT\u002B11',
+ aliases: [],
+ location: null,
+ },
+ {
+ id: 'Etc/GMT\u002B12',
+ aliases: [],
+ location: null,
+ },
+ {
+ id: 'Etc/GMT\u002B2',
+ aliases: [],
+ location: null,
+ },
+ {
+ id: 'Etc/GMT\u002B3',
+ aliases: [],
+ location: null,
+ },
+ {
+ id: 'Etc/GMT\u002B4',
+ aliases: [],
+ location: null,
+ },
+ {
+ id: 'Etc/GMT\u002B5',
+ aliases: [],
+ location: null,
+ },
+ {
+ id: 'Etc/GMT\u002B6',
+ aliases: [],
+ location: null,
+ },
+ {
+ id: 'Etc/GMT\u002B7',
+ aliases: [],
+ location: null,
+ },
+ {
+ id: 'Etc/GMT\u002B8',
+ aliases: [],
+ location: null,
+ },
+ {
+ id: 'Etc/GMT\u002B9',
+ aliases: [],
+ location: null,
+ },
+ {
+ id: 'Etc/UTC',
+ aliases: [
+ 'Etc/UCT',
+ 'Etc/Universal',
+ 'Etc/Zulu',
+ 'UCT',
+ 'UTC',
+ 'Universal',
+ 'Zulu',
+ ],
+ location: null,
+ },
+ {
+ id: 'Europe/Andorra',
+ aliases: [],
+ location: {
+ countryCode: 'AD',
+ countryName: 'Andorra',
+ comment: '',
+ latitude: 42.5,
+ longitude: 1.5166666666666666,
+ },
+ },
+ {
+ id: 'Europe/Astrakhan',
+ aliases: [],
+ location: {
+ countryCode: 'RU',
+ countryName: 'Russia',
+ comment: 'MSK\u002B01 - Astrakhan',
+ latitude: 46.35,
+ longitude: 48.05,
+ },
+ },
+ {
+ id: 'Europe/Athens',
+ aliases: ['EET'],
+ location: {
+ countryCode: 'GR',
+ countryName: 'Greece',
+ comment: '',
+ latitude: 37.96666666666667,
+ longitude: 23.716666666666665,
+ },
+ },
+ {
+ id: 'Europe/Belgrade',
+ aliases: [
+ 'Europe/Ljubljana',
+ 'Europe/Podgorica',
+ 'Europe/Sarajevo',
+ 'Europe/Skopje',
+ 'Europe/Zagreb',
+ ],
+ location: {
+ countryCode: 'RS',
+ countryName: 'Serbia',
+ comment: '',
+ latitude: 44.833333333333336,
+ longitude: 20.5,
+ },
+ },
+ {
+ id: 'Europe/Berlin',
+ aliases: [
+ 'Arctic/Longyearbyen',
+ 'Atlantic/Jan_Mayen',
+ 'Europe/Copenhagen',
+ 'Europe/Oslo',
+ 'Europe/Stockholm',
+ ],
+ location: {
+ countryCode: 'DE',
+ countryName: 'Germany',
+ comment: 'most of Germany',
+ latitude: 52.5,
+ longitude: 13.366666666666667,
+ },
+ },
+ {
+ id: 'Europe/Brussels',
+ aliases: ['CET', 'Europe/Amsterdam', 'Europe/Luxembourg', 'MET'],
+ location: {
+ countryCode: 'BE',
+ countryName: 'Belgium',
+ comment: '',
+ latitude: 50.833333333333336,
+ longitude: 4.333333333333333,
+ },
+ },
+ {
+ id: 'Europe/Bucharest',
+ aliases: [],
+ location: {
+ countryCode: 'RO',
+ countryName: 'Romania',
+ comment: '',
+ latitude: 44.43333333333333,
+ longitude: 26.1,
+ },
+ },
+ {
+ id: 'Europe/Budapest',
+ aliases: [],
+ location: {
+ countryCode: 'HU',
+ countryName: 'Hungary',
+ comment: '',
+ latitude: 47.5,
+ longitude: 19.083333333333332,
+ },
+ },
+ {
+ id: 'Europe/Chisinau',
+ aliases: ['Europe/Tiraspol'],
+ location: {
+ countryCode: 'MD',
+ countryName: 'Moldova',
+ comment: '',
+ latitude: 47,
+ longitude: 28.833333333333332,
+ },
+ },
+ {
+ id: 'Europe/Dublin',
+ aliases: ['Eire'],
+ location: {
+ countryCode: 'IE',
+ countryName: 'Ireland',
+ comment: '',
+ latitude: 53.333333333333336,
+ longitude: -6.25,
+ },
+ },
+ {
+ id: 'Europe/Gibraltar',
+ aliases: [],
+ location: {
+ countryCode: 'GI',
+ countryName: 'Gibraltar',
+ comment: '',
+ latitude: 36.13333333333333,
+ longitude: -5.35,
+ },
+ },
+ {
+ id: 'Europe/Helsinki',
+ aliases: ['Europe/Mariehamn'],
+ location: {
+ countryCode: 'FI',
+ countryName: 'Finland',
+ comment: '',
+ latitude: 60.166666666666664,
+ longitude: 24.966666666666665,
+ },
+ },
+ {
+ id: 'Europe/Istanbul',
+ aliases: ['Asia/Istanbul', 'Turkey'],
+ location: {
+ countryCode: 'TR',
+ countryName: 'Turkey',
+ comment: '',
+ latitude: 41.016666666666666,
+ longitude: 28.966666666666665,
+ },
+ },
+ {
+ id: 'Europe/Kaliningrad',
+ aliases: [],
+ location: {
+ countryCode: 'RU',
+ countryName: 'Russia',
+ comment: 'MSK-01 - Kaliningrad',
+ latitude: 54.71666666666667,
+ longitude: 20.5,
+ },
+ },
+ {
+ id: 'Europe/Kirov',
+ aliases: [],
+ location: {
+ countryCode: 'RU',
+ countryName: 'Russia',
+ comment: 'MSK\u002B00 - Kirov',
+ latitude: 58.6,
+ longitude: 49.65,
+ },
+ },
+ {
+ id: 'Europe/Kyiv',
+ aliases: ['Europe/Kiev', 'Europe/Uzhgorod', 'Europe/Zaporozhye'],
+ location: {
+ countryCode: 'UA',
+ countryName: 'Ukraine',
+ comment: 'most of Ukraine',
+ latitude: 50.43333333333333,
+ longitude: 30.516666666666666,
+ },
+ },
+ {
+ id: 'Europe/Lisbon',
+ aliases: ['Portugal', 'WET'],
+ location: {
+ countryCode: 'PT',
+ countryName: 'Portugal',
+ comment: 'Portugal (mainland)',
+ latitude: 38.71666666666667,
+ longitude: -9.133333333333333,
+ },
+ },
+ {
+ id: 'Europe/London',
+ aliases: [
+ 'Europe/Belfast',
+ 'Europe/Guernsey',
+ 'Europe/Isle_of_Man',
+ 'Europe/Jersey',
+ 'GB',
+ 'GB-Eire',
+ ],
+ location: {
+ countryCode: 'GB',
+ countryName: 'Britain (UK)',
+ comment: '',
+ latitude: 51.50833333333333,
+ longitude: -0.12527777777777777,
+ },
+ },
+ {
+ id: 'Europe/Madrid',
+ aliases: [],
+ location: {
+ countryCode: 'ES',
+ countryName: 'Spain',
+ comment: 'Spain (mainland)',
+ latitude: 40.4,
+ longitude: -3.683333333333333,
+ },
+ },
+ {
+ id: 'Europe/Malta',
+ aliases: [],
+ location: {
+ countryCode: 'MT',
+ countryName: 'Malta',
+ comment: '',
+ latitude: 35.9,
+ longitude: 14.516666666666667,
+ },
+ },
+ {
+ id: 'Europe/Minsk',
+ aliases: [],
+ location: {
+ countryCode: 'BY',
+ countryName: 'Belarus',
+ comment: '',
+ latitude: 53.9,
+ longitude: 27.566666666666666,
+ },
+ },
+ {
+ id: 'Europe/Moscow',
+ aliases: ['W-SU'],
+ location: {
+ countryCode: 'RU',
+ countryName: 'Russia',
+ comment: 'MSK\u002B00 - Moscow area',
+ latitude: 55.755833333333335,
+ longitude: 37.617777777777775,
+ },
+ },
+ {
+ id: 'Europe/Paris',
+ aliases: ['Europe/Monaco'],
+ location: {
+ countryCode: 'FR',
+ countryName: 'France',
+ comment: '',
+ latitude: 48.86666666666667,
+ longitude: 2.3333333333333335,
+ },
+ },
+ {
+ id: 'Europe/Prague',
+ aliases: ['Europe/Bratislava'],
+ location: {
+ countryCode: 'CZ',
+ countryName: 'Czech Republic',
+ comment: '',
+ latitude: 50.083333333333336,
+ longitude: 14.433333333333334,
+ },
+ },
+ {
+ id: 'Europe/Riga',
+ aliases: [],
+ location: {
+ countryCode: 'LV',
+ countryName: 'Latvia',
+ comment: '',
+ latitude: 56.95,
+ longitude: 24.1,
+ },
+ },
+ {
+ id: 'Europe/Rome',
+ aliases: ['Europe/San_Marino', 'Europe/Vatican'],
+ location: {
+ countryCode: 'IT',
+ countryName: 'Italy',
+ comment: '',
+ latitude: 41.9,
+ longitude: 12.483333333333333,
+ },
+ },
+ {
+ id: 'Europe/Samara',
+ aliases: [],
+ location: {
+ countryCode: 'RU',
+ countryName: 'Russia',
+ comment: 'MSK\u002B01 - Samara, Udmurtia',
+ latitude: 53.2,
+ longitude: 50.15,
+ },
+ },
+ {
+ id: 'Europe/Saratov',
+ aliases: [],
+ location: {
+ countryCode: 'RU',
+ countryName: 'Russia',
+ comment: 'MSK\u002B01 - Saratov',
+ latitude: 51.56666666666667,
+ longitude: 46.03333333333333,
+ },
+ },
+ {
+ id: 'Europe/Simferopol',
+ aliases: [],
+ location: {
+ countryCode: 'UA',
+ countryName: 'Ukraine',
+ comment: 'Crimea',
+ latitude: 44.95,
+ longitude: 34.1,
+ },
+ },
+ {
+ id: 'Europe/Sofia',
+ aliases: [],
+ location: {
+ countryCode: 'BG',
+ countryName: 'Bulgaria',
+ comment: '',
+ latitude: 42.68333333333333,
+ longitude: 23.316666666666666,
+ },
+ },
+ {
+ id: 'Europe/Tallinn',
+ aliases: [],
+ location: {
+ countryCode: 'EE',
+ countryName: 'Estonia',
+ comment: '',
+ latitude: 59.416666666666664,
+ longitude: 24.75,
+ },
+ },
+ {
+ id: 'Europe/Tirane',
+ aliases: [],
+ location: {
+ countryCode: 'AL',
+ countryName: 'Albania',
+ comment: '',
+ latitude: 41.333333333333336,
+ longitude: 19.833333333333332,
+ },
+ },
+ {
+ id: 'Europe/Ulyanovsk',
+ aliases: [],
+ location: {
+ countryCode: 'RU',
+ countryName: 'Russia',
+ comment: 'MSK\u002B01 - Ulyanovsk',
+ latitude: 54.333333333333336,
+ longitude: 48.4,
+ },
+ },
+ {
+ id: 'Europe/Vienna',
+ aliases: [],
+ location: {
+ countryCode: 'AT',
+ countryName: 'Austria',
+ comment: '',
+ latitude: 48.21666666666667,
+ longitude: 16.333333333333332,
+ },
+ },
+ {
+ id: 'Europe/Vilnius',
+ aliases: [],
+ location: {
+ countryCode: 'LT',
+ countryName: 'Lithuania',
+ comment: '',
+ latitude: 54.68333333333333,
+ longitude: 25.316666666666666,
+ },
+ },
+ {
+ id: 'Europe/Volgograd',
+ aliases: [],
+ location: {
+ countryCode: 'RU',
+ countryName: 'Russia',
+ comment: 'MSK\u002B00 - Volgograd',
+ latitude: 48.733333333333334,
+ longitude: 44.416666666666664,
+ },
+ },
+ {
+ id: 'Europe/Warsaw',
+ aliases: ['Poland'],
+ location: {
+ countryCode: 'PL',
+ countryName: 'Poland',
+ comment: '',
+ latitude: 52.25,
+ longitude: 21,
+ },
+ },
+ {
+ id: 'Europe/Zurich',
+ aliases: ['Europe/Busingen', 'Europe/Vaduz'],
+ location: {
+ countryCode: 'CH',
+ countryName: 'Switzerland',
+ comment: '',
+ latitude: 47.38333333333333,
+ longitude: 8.533333333333333,
+ },
+ },
+ {
+ id: 'Indian/Chagos',
+ aliases: [],
+ location: {
+ countryCode: 'IO',
+ countryName: 'British Indian Ocean Territory',
+ comment: '',
+ latitude: -7.333333333333333,
+ longitude: 72.41666666666667,
+ },
+ },
+ {
+ id: 'Indian/Maldives',
+ aliases: ['Indian/Kerguelen'],
+ location: {
+ countryCode: 'MV',
+ countryName: 'Maldives',
+ comment: '',
+ latitude: 4.166666666666667,
+ longitude: 73.5,
+ },
+ },
+ {
+ id: 'Indian/Mauritius',
+ aliases: [],
+ location: {
+ countryCode: 'MU',
+ countryName: 'Mauritius',
+ comment: '',
+ latitude: -20.166666666666668,
+ longitude: 57.5,
+ },
+ },
+ {
+ id: 'Pacific/Apia',
+ aliases: [],
+ location: {
+ countryCode: 'WS',
+ countryName: 'Samoa (western)',
+ comment: '',
+ latitude: -13.833333333333334,
+ longitude: -171.73333333333332,
+ },
+ },
+ {
+ id: 'Pacific/Auckland',
+ aliases: ['Antarctica/McMurdo', 'Antarctica/South_Pole', 'NZ'],
+ location: {
+ countryCode: 'NZ',
+ countryName: 'New Zealand',
+ comment: 'most of New Zealand',
+ latitude: -36.86666666666667,
+ longitude: 174.76666666666668,
+ },
+ },
+ {
+ id: 'Pacific/Bougainville',
+ aliases: [],
+ location: {
+ countryCode: 'PG',
+ countryName: 'Papua New Guinea',
+ comment: 'Bougainville',
+ latitude: -6.216666666666667,
+ longitude: 155.56666666666666,
+ },
+ },
+ {
+ id: 'Pacific/Chatham',
+ aliases: ['NZ-CHAT'],
+ location: {
+ countryCode: 'NZ',
+ countryName: 'New Zealand',
+ comment: 'Chatham Islands',
+ latitude: -43.95,
+ longitude: -176.55,
+ },
+ },
+ {
+ id: 'Pacific/Easter',
+ aliases: ['Chile/EasterIsland'],
+ location: {
+ countryCode: 'CL',
+ countryName: 'Chile',
+ comment: 'Easter Island',
+ latitude: -27.15,
+ longitude: -109.43333333333334,
+ },
+ },
+ {
+ id: 'Pacific/Efate',
+ aliases: [],
+ location: {
+ countryCode: 'VU',
+ countryName: 'Vanuatu',
+ comment: '',
+ latitude: -17.666666666666668,
+ longitude: 168.41666666666666,
+ },
+ },
+ {
+ id: 'Pacific/Fakaofo',
+ aliases: [],
+ location: {
+ countryCode: 'TK',
+ countryName: 'Tokelau',
+ comment: '',
+ latitude: -9.366666666666667,
+ longitude: -171.23333333333332,
+ },
+ },
+ {
+ id: 'Pacific/Fiji',
+ aliases: [],
+ location: {
+ countryCode: 'FJ',
+ countryName: 'Fiji',
+ comment: '',
+ latitude: -18.133333333333333,
+ longitude: 178.41666666666666,
+ },
+ },
+ {
+ id: 'Pacific/Galapagos',
+ aliases: [],
+ location: {
+ countryCode: 'EC',
+ countryName: 'Ecuador',
+ comment: 'Galapagos Islands',
+ latitude: -0.9,
+ longitude: -89.6,
+ },
+ },
+ {
+ id: 'Pacific/Gambier',
+ aliases: [],
+ location: {
+ countryCode: 'PF',
+ countryName: 'French Polynesia',
+ comment: 'Gambier Islands',
+ latitude: -23.133333333333333,
+ longitude: -134.95,
+ },
+ },
+ {
+ id: 'Pacific/Guadalcanal',
+ aliases: ['Pacific/Pohnpei', 'Pacific/Ponape'],
+ location: {
+ countryCode: 'SB',
+ countryName: 'Solomon Islands',
+ comment: '',
+ latitude: -9.533333333333333,
+ longitude: 160.2,
+ },
+ },
+ {
+ id: 'Pacific/Guam',
+ aliases: ['Pacific/Saipan'],
+ location: {
+ countryCode: 'GU',
+ countryName: 'Guam',
+ comment: '',
+ latitude: 13.466666666666667,
+ longitude: 144.75,
+ },
+ },
+ {
+ id: 'Pacific/Honolulu',
+ aliases: ['HST', 'Pacific/Johnston', 'US/Hawaii'],
+ location: {
+ countryCode: 'US',
+ countryName: 'United States',
+ comment: 'Hawaii',
+ latitude: 21.306944444444444,
+ longitude: -157.85833333333332,
+ },
+ },
+ {
+ id: 'Pacific/Kanton',
+ aliases: ['Pacific/Enderbury'],
+ location: {
+ countryCode: 'KI',
+ countryName: 'Kiribati',
+ comment: 'Phoenix Islands',
+ latitude: -2.783333333333333,
+ longitude: -171.71666666666667,
+ },
+ },
+ {
+ id: 'Pacific/Kiritimati',
+ aliases: [],
+ location: {
+ countryCode: 'KI',
+ countryName: 'Kiribati',
+ comment: 'Line Islands',
+ latitude: 1.8666666666666667,
+ longitude: -157.33333333333334,
+ },
+ },
+ {
+ id: 'Pacific/Kosrae',
+ aliases: [],
+ location: {
+ countryCode: 'FM',
+ countryName: 'Micronesia',
+ comment: 'Kosrae',
+ latitude: 5.316666666666666,
+ longitude: 162.98333333333332,
+ },
+ },
+ {
+ id: 'Pacific/Kwajalein',
+ aliases: ['Kwajalein'],
+ location: {
+ countryCode: 'MH',
+ countryName: 'Marshall Islands',
+ comment: 'Kwajalein',
+ latitude: 9.083333333333334,
+ longitude: 167.33333333333334,
+ },
+ },
+ {
+ id: 'Pacific/Marquesas',
+ aliases: [],
+ location: {
+ countryCode: 'PF',
+ countryName: 'French Polynesia',
+ comment: 'Marquesas Islands',
+ latitude: -9,
+ longitude: -139.5,
+ },
+ },
+ {
+ id: 'Pacific/Nauru',
+ aliases: [],
+ location: {
+ countryCode: 'NR',
+ countryName: 'Nauru',
+ comment: '',
+ latitude: -0.5166666666666667,
+ longitude: 166.91666666666666,
+ },
+ },
+ {
+ id: 'Pacific/Niue',
+ aliases: [],
+ location: {
+ countryCode: 'NU',
+ countryName: 'Niue',
+ comment: '',
+ latitude: -19.016666666666666,
+ longitude: -169.91666666666666,
+ },
+ },
+ {
+ id: 'Pacific/Norfolk',
+ aliases: [],
+ location: {
+ countryCode: 'NF',
+ countryName: 'Norfolk Island',
+ comment: '',
+ latitude: -29.05,
+ longitude: 167.96666666666667,
+ },
+ },
+ {
+ id: 'Pacific/Noumea',
+ aliases: [],
+ location: {
+ countryCode: 'NC',
+ countryName: 'New Caledonia',
+ comment: '',
+ latitude: -22.266666666666666,
+ longitude: 166.45,
+ },
+ },
+ {
+ id: 'Pacific/Pago_Pago',
+ aliases: ['Pacific/Midway', 'Pacific/Samoa', 'US/Samoa'],
+ location: {
+ countryCode: 'AS',
+ countryName: 'Samoa (American)',
+ comment: '',
+ latitude: -14.266666666666667,
+ longitude: -170.7,
+ },
+ },
+ {
+ id: 'Pacific/Palau',
+ aliases: [],
+ location: {
+ countryCode: 'PW',
+ countryName: 'Palau',
+ comment: '',
+ latitude: 7.333333333333333,
+ longitude: 134.48333333333332,
+ },
+ },
+ {
+ id: 'Pacific/Pitcairn',
+ aliases: [],
+ location: {
+ countryCode: 'PN',
+ countryName: 'Pitcairn',
+ comment: '',
+ latitude: -25.066666666666666,
+ longitude: -130.08333333333334,
+ },
+ },
+ {
+ id: 'Pacific/Port_Moresby',
+ aliases: [
+ 'Antarctica/DumontDUrville',
+ 'Pacific/Chuuk',
+ 'Pacific/Truk',
+ 'Pacific/Yap',
+ ],
+ location: {
+ countryCode: 'PG',
+ countryName: 'Papua New Guinea',
+ comment: 'most of Papua New Guinea',
+ latitude: -9.5,
+ longitude: 147.16666666666666,
+ },
+ },
+ {
+ id: 'Pacific/Rarotonga',
+ aliases: [],
+ location: {
+ countryCode: 'CK',
+ countryName: 'Cook Islands',
+ comment: '',
+ latitude: -21.233333333333334,
+ longitude: -159.76666666666668,
+ },
+ },
+ {
+ id: 'Pacific/Tahiti',
+ aliases: [],
+ location: {
+ countryCode: 'PF',
+ countryName: 'French Polynesia',
+ comment: 'Society Islands',
+ latitude: -17.533333333333335,
+ longitude: -149.56666666666666,
+ },
+ },
+ {
+ id: 'Pacific/Tarawa',
+ aliases: [
+ 'Pacific/Funafuti',
+ 'Pacific/Majuro',
+ 'Pacific/Wake',
+ 'Pacific/Wallis',
+ ],
+ location: {
+ countryCode: 'KI',
+ countryName: 'Kiribati',
+ comment: 'Gilbert Islands',
+ latitude: 1.4166666666666667,
+ longitude: 173,
+ },
+ },
+ {
+ id: 'Pacific/Tongatapu',
+ aliases: [],
+ location: {
+ countryCode: 'TO',
+ countryName: 'Tonga',
+ comment: '',
+ latitude: -21.133333333333333,
+ longitude: -175.2,
+ },
+ },
+ ],
+} as const;
+
+type TimeZoneIds = (typeof timezoneData)['zones'][number]['id'];
+type TimeZoneAlias = (typeof timezoneData)['zones'][number]['aliases'][number];
+export type TimeZones = TimeZoneIds | TimeZoneAlias;
+
+export const timeZoneIds = timezoneData.zones.map((zone) => zone.id) as TimeZoneIds[];
+export const timeZoneAliases = timezoneData.zones.flatMap((zone) => zone.aliases) as TimeZoneAlias[];
+export const allTimeZones = [...timeZoneIds, ...timeZoneAliases] as const;
\ No newline at end of file
From 4cf5ce26ffc43f00e5bf69ef202581ac5792f6af Mon Sep 17 00:00:00 2001
From: Dominik Stahl
Date: Mon, 23 Jun 2025 10:44:26 +0200
Subject: [PATCH 06/16] feat(api): implement DELETE method for /api/user/me
endpoint
---
src/app/api/user/me/route.ts | 41 ++++++++++++++++++++++++++++++++++
src/app/api/user/me/swagger.ts | 21 +++++++++++++++++
2 files changed, 62 insertions(+)
diff --git a/src/app/api/user/me/route.ts b/src/app/api/user/me/route.ts
index 5ba9792..dcd3bc3 100644
--- a/src/app/api/user/me/route.ts
+++ b/src/app/api/user/me/route.ts
@@ -8,6 +8,7 @@ import {
import { FullUserResponseSchema } from '../validation';
import {
ErrorResponseSchema,
+ SuccessResponseSchema,
ZodErrorResponseSchema,
} from '@/app/api/validation';
@@ -117,3 +118,43 @@ export const PATCH = auth(async function PATCH(req) {
{ status: 200 },
);
});
+
+export const DELETE = auth(async function DELETE(req) {
+ const authCheck = userAuthenticated(req);
+ if (!authCheck.continue)
+ return returnZodTypeCheckedResponse(
+ ErrorResponseSchema,
+ authCheck.response,
+ authCheck.metadata,
+ );
+
+ const dbUser = await prisma.user.findUnique({
+ where: {
+ id: authCheck.user.id,
+ },
+ });
+ if (!dbUser)
+ return returnZodTypeCheckedResponse(
+ ErrorResponseSchema,
+ {
+ success: false,
+ message: 'User not found',
+ },
+ { status: 404 },
+ );
+
+ await prisma.user.delete({
+ where: {
+ id: authCheck.user.id,
+ },
+ });
+
+ return returnZodTypeCheckedResponse(
+ SuccessResponseSchema,
+ {
+ success: true,
+ message: 'User deleted successfully',
+ },
+ { status: 200 },
+ );
+});
\ No newline at end of file
diff --git a/src/app/api/user/me/swagger.ts b/src/app/api/user/me/swagger.ts
index e0a36a1..6a9e375 100644
--- a/src/app/api/user/me/swagger.ts
+++ b/src/app/api/user/me/swagger.ts
@@ -7,6 +7,7 @@ import {
serverReturnedDataValidationErrorResponse,
userNotFoundResponse,
} from '@/lib/defaultApiResponses';
+import { SuccessResponseSchema } from '../../validation';
export default function registerSwaggerPaths(registry: OpenAPIRegistry) {
registry.registerPath({
@@ -60,4 +61,24 @@ export default function registerSwaggerPaths(registry: OpenAPIRegistry) {
},
tags: ['User'],
});
+
+ registry.registerPath({
+ method: 'delete',
+ path: '/api/user/me',
+ description: 'Delete the currently authenticated user',
+ responses: {
+ 200: {
+ description: 'User deleted successfully',
+ content: {
+ 'application/json': {
+ schema: SuccessResponseSchema,
+ },
+ },
+ },
+ ...notAuthenticatedResponse,
+ ...userNotFoundResponse,
+ ...serverReturnedDataValidationErrorResponse,
+ },
+ tags: ['User'],
+ });
}
From 29f2a01ac69b47c80f520afc2e4df5f91eb40ca5 Mon Sep 17 00:00:00 2001
From: Dominik Stahl
Date: Mon, 23 Jun 2025 10:45:56 +0200
Subject: [PATCH 07/16] style: format code
---
src/app/api/user/me/password/route.ts | 5 ++++-
src/app/api/user/me/route.ts | 2 +-
src/app/api/user/me/validation.ts | 16 +++++++++-------
src/app/api/user/validation.ts | 13 +++++++------
src/lib/timezones.ts | 10 +++++++---
5 files changed, 28 insertions(+), 18 deletions(-)
diff --git a/src/app/api/user/me/password/route.ts b/src/app/api/user/me/password/route.ts
index 03fb426..0b92559 100644
--- a/src/app/api/user/me/password/route.ts
+++ b/src/app/api/user/me/password/route.ts
@@ -65,7 +65,10 @@ export const PATCH = auth(async function PATCH(req) {
{ status: 400 },
);
- if (dbUser.accounts.length === 0 || dbUser.accounts[0].provider !== 'credentials')
+ if (
+ dbUser.accounts.length === 0 ||
+ dbUser.accounts[0].provider !== 'credentials'
+ )
return returnZodTypeCheckedResponse(
ErrorResponseSchema,
{
diff --git a/src/app/api/user/me/route.ts b/src/app/api/user/me/route.ts
index dcd3bc3..5571a6b 100644
--- a/src/app/api/user/me/route.ts
+++ b/src/app/api/user/me/route.ts
@@ -157,4 +157,4 @@ export const DELETE = auth(async function DELETE(req) {
},
{ status: 200 },
);
-});
\ No newline at end of file
+});
diff --git a/src/app/api/user/me/validation.ts b/src/app/api/user/me/validation.ts
index 2e8ff0a..66f07cc 100644
--- a/src/app/api/user/me/validation.ts
+++ b/src/app/api/user/me/validation.ts
@@ -22,10 +22,12 @@ export const updateUserServerSchema = zod.object({
timezone: timezoneSchema.optional(),
});
-export const updateUserPasswordServerSchema = zod.object({
- current_password: zod.string().min(1, 'Current password is required'),
- new_password: passwordSchema,
- confirm_new_password: passwordSchema,
-}).refine((data) => data.new_password === data.confirm_new_password, {
- message: 'New password and confirm new password must match',
-});
+export const updateUserPasswordServerSchema = zod
+ .object({
+ current_password: zod.string().min(1, 'Current password is required'),
+ new_password: passwordSchema,
+ confirm_new_password: passwordSchema,
+ })
+ .refine((data) => data.new_password === data.confirm_new_password, {
+ message: 'New password and confirm new password must match',
+ });
diff --git a/src/app/api/user/validation.ts b/src/app/api/user/validation.ts
index 100470e..89b8ba4 100644
--- a/src/app/api/user/validation.ts
+++ b/src/app/api/user/validation.ts
@@ -113,11 +113,9 @@ export const passwordSchema = zod
// Timezone Validation
//
// ----------------------------------------
-export const timezoneSchema = zod
- .enum(allTimeZones)
- .openapi('Timezone', {
- description: 'Valid timezone from the list of supported timezones',
- });
+export const timezoneSchema = zod.enum(allTimeZones).openapi('Timezone', {
+ description: 'Valid timezone from the list of supported timezones',
+});
// ----------------------------------------
//
@@ -132,7 +130,10 @@ export const FullUserSchema = zod
last_name: zod.string().nullish(),
email: zod.email(),
image: zod.url().nullish(),
- timezone: zod.string().refine((i) => (allTimeZones as string[]).includes(i)).nullish(),
+ timezone: zod
+ .string()
+ .refine((i) => (allTimeZones as string[]).includes(i))
+ .nullish(),
created_at: zod.date(),
updated_at: zod.date(),
})
diff --git a/src/lib/timezones.ts b/src/lib/timezones.ts
index e1d681a..9382aab 100644
--- a/src/lib/timezones.ts
+++ b/src/lib/timezones.ts
@@ -3708,6 +3708,10 @@ type TimeZoneIds = (typeof timezoneData)['zones'][number]['id'];
type TimeZoneAlias = (typeof timezoneData)['zones'][number]['aliases'][number];
export type TimeZones = TimeZoneIds | TimeZoneAlias;
-export const timeZoneIds = timezoneData.zones.map((zone) => zone.id) as TimeZoneIds[];
-export const timeZoneAliases = timezoneData.zones.flatMap((zone) => zone.aliases) as TimeZoneAlias[];
-export const allTimeZones = [...timeZoneIds, ...timeZoneAliases] as const;
\ No newline at end of file
+export const timeZoneIds = timezoneData.zones.map(
+ (zone) => zone.id,
+) as TimeZoneIds[];
+export const timeZoneAliases = timezoneData.zones.flatMap(
+ (zone) => zone.aliases,
+) as TimeZoneAlias[];
+export const allTimeZones = [...timeZoneIds, ...timeZoneAliases] as const;
From 3ee0dcf950130d77d97feaaeb9f0e3fd0fc51438 Mon Sep 17 00:00:00 2001
From: Renovate Bot
Date: Mon, 23 Jun 2025 11:01:37 +0000
Subject: [PATCH 08/16] fix(deps): update dependency next to v15.4.0-canary.93
---
package.json | 2 +-
yarn.lock | 84 ++++++++++++++++++++++++++--------------------------
2 files changed, 43 insertions(+), 43 deletions(-)
diff --git a/package.json b/package.json
index c5c77fb..5cb263e 100644
--- a/package.json
+++ b/package.json
@@ -41,7 +41,7 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.511.0",
- "next": "15.4.0-canary.92",
+ "next": "15.4.0-canary.93",
"next-auth": "^5.0.0-beta.25",
"next-themes": "^0.4.6",
"react": "^19.0.0",
diff --git a/yarn.lock b/yarn.lock
index 56de1d1..650108b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -939,10 +939,10 @@ __metadata:
languageName: node
linkType: hard
-"@next/env@npm:15.4.0-canary.92":
- version: 15.4.0-canary.92
- resolution: "@next/env@npm:15.4.0-canary.92"
- checksum: 10c0/5d3df767dab8227aa64058b5082977b069ee7c22038b13b1da1471a4b312bca7b0163cf50f130f22a850b479aca8b9fea649d02720fc24f49528fd70751554c9
+"@next/env@npm:15.4.0-canary.93":
+ version: 15.4.0-canary.93
+ resolution: "@next/env@npm:15.4.0-canary.93"
+ checksum: 10c0/c6ac8755baf784bc7bec90c64f33c60882d79a46806dc80206ec32e6472b61b00e5ddc75c8de1cf053b248ec372919f4a76e8f00d231ed133376607868e4ad48
languageName: node
linkType: hard
@@ -955,58 +955,58 @@ __metadata:
languageName: node
linkType: hard
-"@next/swc-darwin-arm64@npm:15.4.0-canary.92":
- version: 15.4.0-canary.92
- resolution: "@next/swc-darwin-arm64@npm:15.4.0-canary.92"
+"@next/swc-darwin-arm64@npm:15.4.0-canary.93":
+ version: 15.4.0-canary.93
+ resolution: "@next/swc-darwin-arm64@npm:15.4.0-canary.93"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
-"@next/swc-darwin-x64@npm:15.4.0-canary.92":
- version: 15.4.0-canary.92
- resolution: "@next/swc-darwin-x64@npm:15.4.0-canary.92"
+"@next/swc-darwin-x64@npm:15.4.0-canary.93":
+ version: 15.4.0-canary.93
+ resolution: "@next/swc-darwin-x64@npm:15.4.0-canary.93"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
-"@next/swc-linux-arm64-gnu@npm:15.4.0-canary.92":
- version: 15.4.0-canary.92
- resolution: "@next/swc-linux-arm64-gnu@npm:15.4.0-canary.92"
+"@next/swc-linux-arm64-gnu@npm:15.4.0-canary.93":
+ version: 15.4.0-canary.93
+ resolution: "@next/swc-linux-arm64-gnu@npm:15.4.0-canary.93"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
-"@next/swc-linux-arm64-musl@npm:15.4.0-canary.92":
- version: 15.4.0-canary.92
- resolution: "@next/swc-linux-arm64-musl@npm:15.4.0-canary.92"
+"@next/swc-linux-arm64-musl@npm:15.4.0-canary.93":
+ version: 15.4.0-canary.93
+ resolution: "@next/swc-linux-arm64-musl@npm:15.4.0-canary.93"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
-"@next/swc-linux-x64-gnu@npm:15.4.0-canary.92":
- version: 15.4.0-canary.92
- resolution: "@next/swc-linux-x64-gnu@npm:15.4.0-canary.92"
+"@next/swc-linux-x64-gnu@npm:15.4.0-canary.93":
+ version: 15.4.0-canary.93
+ resolution: "@next/swc-linux-x64-gnu@npm:15.4.0-canary.93"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
-"@next/swc-linux-x64-musl@npm:15.4.0-canary.92":
- version: 15.4.0-canary.92
- resolution: "@next/swc-linux-x64-musl@npm:15.4.0-canary.92"
+"@next/swc-linux-x64-musl@npm:15.4.0-canary.93":
+ version: 15.4.0-canary.93
+ resolution: "@next/swc-linux-x64-musl@npm:15.4.0-canary.93"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
-"@next/swc-win32-arm64-msvc@npm:15.4.0-canary.92":
- version: 15.4.0-canary.92
- resolution: "@next/swc-win32-arm64-msvc@npm:15.4.0-canary.92"
+"@next/swc-win32-arm64-msvc@npm:15.4.0-canary.93":
+ version: 15.4.0-canary.93
+ resolution: "@next/swc-win32-arm64-msvc@npm:15.4.0-canary.93"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
-"@next/swc-win32-x64-msvc@npm:15.4.0-canary.92":
- version: 15.4.0-canary.92
- resolution: "@next/swc-win32-x64-msvc@npm:15.4.0-canary.92"
+"@next/swc-win32-x64-msvc@npm:15.4.0-canary.93":
+ version: 15.4.0-canary.93
+ resolution: "@next/swc-win32-x64-msvc@npm:15.4.0-canary.93"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
@@ -6573,7 +6573,7 @@ __metadata:
eslint-config-next: "npm:15.3.4"
eslint-config-prettier: "npm:10.1.5"
lucide-react: "npm:^0.511.0"
- next: "npm:15.4.0-canary.92"
+ next: "npm:15.4.0-canary.93"
next-auth: "npm:^5.0.0-beta.25"
next-themes: "npm:^0.4.6"
orval: "npm:7.10.0"
@@ -6856,19 +6856,19 @@ __metadata:
languageName: node
linkType: hard
-"next@npm:15.4.0-canary.92":
- version: 15.4.0-canary.92
- resolution: "next@npm:15.4.0-canary.92"
+"next@npm:15.4.0-canary.93":
+ version: 15.4.0-canary.93
+ resolution: "next@npm:15.4.0-canary.93"
dependencies:
- "@next/env": "npm:15.4.0-canary.92"
- "@next/swc-darwin-arm64": "npm:15.4.0-canary.92"
- "@next/swc-darwin-x64": "npm:15.4.0-canary.92"
- "@next/swc-linux-arm64-gnu": "npm:15.4.0-canary.92"
- "@next/swc-linux-arm64-musl": "npm:15.4.0-canary.92"
- "@next/swc-linux-x64-gnu": "npm:15.4.0-canary.92"
- "@next/swc-linux-x64-musl": "npm:15.4.0-canary.92"
- "@next/swc-win32-arm64-msvc": "npm:15.4.0-canary.92"
- "@next/swc-win32-x64-msvc": "npm:15.4.0-canary.92"
+ "@next/env": "npm:15.4.0-canary.93"
+ "@next/swc-darwin-arm64": "npm:15.4.0-canary.93"
+ "@next/swc-darwin-x64": "npm:15.4.0-canary.93"
+ "@next/swc-linux-arm64-gnu": "npm:15.4.0-canary.93"
+ "@next/swc-linux-arm64-musl": "npm:15.4.0-canary.93"
+ "@next/swc-linux-x64-gnu": "npm:15.4.0-canary.93"
+ "@next/swc-linux-x64-musl": "npm:15.4.0-canary.93"
+ "@next/swc-win32-arm64-msvc": "npm:15.4.0-canary.93"
+ "@next/swc-win32-x64-msvc": "npm:15.4.0-canary.93"
"@swc/helpers": "npm:0.5.15"
caniuse-lite: "npm:^1.0.30001579"
postcss: "npm:8.4.31"
@@ -6911,7 +6911,7 @@ __metadata:
optional: true
bin:
next: dist/bin/next
- checksum: 10c0/78b76ba1af4e0c11a1839142f774b9572814f524798d6a27c64180bfe3679ad93af6d3947260b66d02d1fa407a5da3523ca688bcff7fbc939e63559c9587069e
+ checksum: 10c0/d54f7a7cb76a3ca5aa22d477f8403fccb3c60b9335fea7ed03ae360701c173ab58fde064af208dfec993089ab77f559f04c996c5b0627c67c0c0ee53aa2bd378
languageName: node
linkType: hard
From 2889424bfba47df757b53a6d3bb63f873a7eb5c6 Mon Sep 17 00:00:00 2001
From: Renovate Bot
Date: Tue, 24 Jun 2025 00:01:37 +0000
Subject: [PATCH 09/16] fix(deps): update dependency next to v15.4.0-canary.94
---
package.json | 2 +-
yarn.lock | 84 ++++++++++++++++++++++++++--------------------------
2 files changed, 43 insertions(+), 43 deletions(-)
diff --git a/package.json b/package.json
index 5cb263e..f9a6d96 100644
--- a/package.json
+++ b/package.json
@@ -41,7 +41,7 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.511.0",
- "next": "15.4.0-canary.93",
+ "next": "15.4.0-canary.94",
"next-auth": "^5.0.0-beta.25",
"next-themes": "^0.4.6",
"react": "^19.0.0",
diff --git a/yarn.lock b/yarn.lock
index 650108b..38156a2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -939,10 +939,10 @@ __metadata:
languageName: node
linkType: hard
-"@next/env@npm:15.4.0-canary.93":
- version: 15.4.0-canary.93
- resolution: "@next/env@npm:15.4.0-canary.93"
- checksum: 10c0/c6ac8755baf784bc7bec90c64f33c60882d79a46806dc80206ec32e6472b61b00e5ddc75c8de1cf053b248ec372919f4a76e8f00d231ed133376607868e4ad48
+"@next/env@npm:15.4.0-canary.94":
+ version: 15.4.0-canary.94
+ resolution: "@next/env@npm:15.4.0-canary.94"
+ checksum: 10c0/d4c4f9fd1ab996271f57b2ace4db53c0f0209ddcfc536dbe1fe28f3dd3bcc858b402abdf69d7ebb778581d94da4034a5a0bef0ced234e4a476fafdcba2ea46a0
languageName: node
linkType: hard
@@ -955,58 +955,58 @@ __metadata:
languageName: node
linkType: hard
-"@next/swc-darwin-arm64@npm:15.4.0-canary.93":
- version: 15.4.0-canary.93
- resolution: "@next/swc-darwin-arm64@npm:15.4.0-canary.93"
+"@next/swc-darwin-arm64@npm:15.4.0-canary.94":
+ version: 15.4.0-canary.94
+ resolution: "@next/swc-darwin-arm64@npm:15.4.0-canary.94"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
-"@next/swc-darwin-x64@npm:15.4.0-canary.93":
- version: 15.4.0-canary.93
- resolution: "@next/swc-darwin-x64@npm:15.4.0-canary.93"
+"@next/swc-darwin-x64@npm:15.4.0-canary.94":
+ version: 15.4.0-canary.94
+ resolution: "@next/swc-darwin-x64@npm:15.4.0-canary.94"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
-"@next/swc-linux-arm64-gnu@npm:15.4.0-canary.93":
- version: 15.4.0-canary.93
- resolution: "@next/swc-linux-arm64-gnu@npm:15.4.0-canary.93"
+"@next/swc-linux-arm64-gnu@npm:15.4.0-canary.94":
+ version: 15.4.0-canary.94
+ resolution: "@next/swc-linux-arm64-gnu@npm:15.4.0-canary.94"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
-"@next/swc-linux-arm64-musl@npm:15.4.0-canary.93":
- version: 15.4.0-canary.93
- resolution: "@next/swc-linux-arm64-musl@npm:15.4.0-canary.93"
+"@next/swc-linux-arm64-musl@npm:15.4.0-canary.94":
+ version: 15.4.0-canary.94
+ resolution: "@next/swc-linux-arm64-musl@npm:15.4.0-canary.94"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
-"@next/swc-linux-x64-gnu@npm:15.4.0-canary.93":
- version: 15.4.0-canary.93
- resolution: "@next/swc-linux-x64-gnu@npm:15.4.0-canary.93"
+"@next/swc-linux-x64-gnu@npm:15.4.0-canary.94":
+ version: 15.4.0-canary.94
+ resolution: "@next/swc-linux-x64-gnu@npm:15.4.0-canary.94"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
-"@next/swc-linux-x64-musl@npm:15.4.0-canary.93":
- version: 15.4.0-canary.93
- resolution: "@next/swc-linux-x64-musl@npm:15.4.0-canary.93"
+"@next/swc-linux-x64-musl@npm:15.4.0-canary.94":
+ version: 15.4.0-canary.94
+ resolution: "@next/swc-linux-x64-musl@npm:15.4.0-canary.94"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
-"@next/swc-win32-arm64-msvc@npm:15.4.0-canary.93":
- version: 15.4.0-canary.93
- resolution: "@next/swc-win32-arm64-msvc@npm:15.4.0-canary.93"
+"@next/swc-win32-arm64-msvc@npm:15.4.0-canary.94":
+ version: 15.4.0-canary.94
+ resolution: "@next/swc-win32-arm64-msvc@npm:15.4.0-canary.94"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
-"@next/swc-win32-x64-msvc@npm:15.4.0-canary.93":
- version: 15.4.0-canary.93
- resolution: "@next/swc-win32-x64-msvc@npm:15.4.0-canary.93"
+"@next/swc-win32-x64-msvc@npm:15.4.0-canary.94":
+ version: 15.4.0-canary.94
+ resolution: "@next/swc-win32-x64-msvc@npm:15.4.0-canary.94"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
@@ -6573,7 +6573,7 @@ __metadata:
eslint-config-next: "npm:15.3.4"
eslint-config-prettier: "npm:10.1.5"
lucide-react: "npm:^0.511.0"
- next: "npm:15.4.0-canary.93"
+ next: "npm:15.4.0-canary.94"
next-auth: "npm:^5.0.0-beta.25"
next-themes: "npm:^0.4.6"
orval: "npm:7.10.0"
@@ -6856,19 +6856,19 @@ __metadata:
languageName: node
linkType: hard
-"next@npm:15.4.0-canary.93":
- version: 15.4.0-canary.93
- resolution: "next@npm:15.4.0-canary.93"
+"next@npm:15.4.0-canary.94":
+ version: 15.4.0-canary.94
+ resolution: "next@npm:15.4.0-canary.94"
dependencies:
- "@next/env": "npm:15.4.0-canary.93"
- "@next/swc-darwin-arm64": "npm:15.4.0-canary.93"
- "@next/swc-darwin-x64": "npm:15.4.0-canary.93"
- "@next/swc-linux-arm64-gnu": "npm:15.4.0-canary.93"
- "@next/swc-linux-arm64-musl": "npm:15.4.0-canary.93"
- "@next/swc-linux-x64-gnu": "npm:15.4.0-canary.93"
- "@next/swc-linux-x64-musl": "npm:15.4.0-canary.93"
- "@next/swc-win32-arm64-msvc": "npm:15.4.0-canary.93"
- "@next/swc-win32-x64-msvc": "npm:15.4.0-canary.93"
+ "@next/env": "npm:15.4.0-canary.94"
+ "@next/swc-darwin-arm64": "npm:15.4.0-canary.94"
+ "@next/swc-darwin-x64": "npm:15.4.0-canary.94"
+ "@next/swc-linux-arm64-gnu": "npm:15.4.0-canary.94"
+ "@next/swc-linux-arm64-musl": "npm:15.4.0-canary.94"
+ "@next/swc-linux-x64-gnu": "npm:15.4.0-canary.94"
+ "@next/swc-linux-x64-musl": "npm:15.4.0-canary.94"
+ "@next/swc-win32-arm64-msvc": "npm:15.4.0-canary.94"
+ "@next/swc-win32-x64-msvc": "npm:15.4.0-canary.94"
"@swc/helpers": "npm:0.5.15"
caniuse-lite: "npm:^1.0.30001579"
postcss: "npm:8.4.31"
@@ -6911,7 +6911,7 @@ __metadata:
optional: true
bin:
next: dist/bin/next
- checksum: 10c0/d54f7a7cb76a3ca5aa22d477f8403fccb3c60b9335fea7ed03ae360701c173ab58fde064af208dfec993089ab77f559f04c996c5b0627c67c0c0ee53aa2bd378
+ checksum: 10c0/4d3a3f0f85524bb0d595a58b61e017dee584024d4190f7f2709a4dd02ff922040e206531a652d89c3e47447c9c2c2e3b84729fd52fb9db1cb88db6c46c52c537
languageName: node
linkType: hard
From d62e9543488ca7e768cc24590637c8c8924746a6 Mon Sep 17 00:00:00 2001
From: Renovate Bot
Date: Tue, 24 Jun 2025 17:01:52 +0000
Subject: [PATCH 10/16] chore(deps): update dependency @types/node to v22.15.33
---
package.json | 2 +-
yarn.lock | 10 +++++-----
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/package.json b/package.json
index f9a6d96..93da188 100644
--- a/package.json
+++ b/package.json
@@ -54,7 +54,7 @@
"devDependencies": {
"@eslint/eslintrc": "3.3.1",
"@tailwindcss/postcss": "4.1.10",
- "@types/node": "22.15.32",
+ "@types/node": "22.15.33",
"@types/react": "19.1.8",
"@types/react-dom": "19.1.6",
"@types/swagger-ui-react": "5",
diff --git a/yarn.lock b/yarn.lock
index 38156a2..d7ba663 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3102,12 +3102,12 @@ __metadata:
languageName: node
linkType: hard
-"@types/node@npm:22.15.32":
- version: 22.15.32
- resolution: "@types/node@npm:22.15.32"
+"@types/node@npm:22.15.33":
+ version: 22.15.33
+ resolution: "@types/node@npm:22.15.33"
dependencies:
undici-types: "npm:~6.21.0"
- checksum: 10c0/63a2fa52adf1134d1b3bee8b1862d4b8e4550fffc190551068d3d41a41d9e5c0c8f1cb81faa18767b260637360f662115c26c5e4e7718868ead40c4a57cbc0e3
+ checksum: 10c0/ee040c29c891aa37fffc27d04a8529318c391356346933646b7692eaf62236831ad532f6ebaf43ebd6a2ef1f0f091860d8a0a83a4e3c5a4f66d37aa1b2c99f31
languageName: node
linkType: hard
@@ -6560,7 +6560,7 @@ __metadata:
"@radix-ui/react-tabs": "npm:^1.1.11"
"@tailwindcss/postcss": "npm:4.1.10"
"@tanstack/react-query": "npm:^5.80.7"
- "@types/node": "npm:22.15.32"
+ "@types/node": "npm:22.15.33"
"@types/react": "npm:19.1.8"
"@types/react-dom": "npm:19.1.6"
"@types/swagger-ui-react": "npm:5"
From 21eff651e8ab77b8de4613ce8d4760ece92deb8b Mon Sep 17 00:00:00 2001
From: Renovate Bot
Date: Wed, 25 Jun 2025 00:01:36 +0000
Subject: [PATCH 11/16] fix(deps): update dependency next to v15.4.0-canary.95
---
package.json | 2 +-
yarn.lock | 84 ++++++++++++++++++++++++++--------------------------
2 files changed, 43 insertions(+), 43 deletions(-)
diff --git a/package.json b/package.json
index 93da188..f943b16 100644
--- a/package.json
+++ b/package.json
@@ -41,7 +41,7 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.511.0",
- "next": "15.4.0-canary.94",
+ "next": "15.4.0-canary.95",
"next-auth": "^5.0.0-beta.25",
"next-themes": "^0.4.6",
"react": "^19.0.0",
diff --git a/yarn.lock b/yarn.lock
index d7ba663..b97a688 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -939,10 +939,10 @@ __metadata:
languageName: node
linkType: hard
-"@next/env@npm:15.4.0-canary.94":
- version: 15.4.0-canary.94
- resolution: "@next/env@npm:15.4.0-canary.94"
- checksum: 10c0/d4c4f9fd1ab996271f57b2ace4db53c0f0209ddcfc536dbe1fe28f3dd3bcc858b402abdf69d7ebb778581d94da4034a5a0bef0ced234e4a476fafdcba2ea46a0
+"@next/env@npm:15.4.0-canary.95":
+ version: 15.4.0-canary.95
+ resolution: "@next/env@npm:15.4.0-canary.95"
+ checksum: 10c0/dfa499393a68293517db690e805074ee2da8667188c7508adbd4c94f2966861c91ba5ec7a4a91b75e29e603fdb299d637d80180c87df6910796c3fa376a02f96
languageName: node
linkType: hard
@@ -955,58 +955,58 @@ __metadata:
languageName: node
linkType: hard
-"@next/swc-darwin-arm64@npm:15.4.0-canary.94":
- version: 15.4.0-canary.94
- resolution: "@next/swc-darwin-arm64@npm:15.4.0-canary.94"
+"@next/swc-darwin-arm64@npm:15.4.0-canary.95":
+ version: 15.4.0-canary.95
+ resolution: "@next/swc-darwin-arm64@npm:15.4.0-canary.95"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
-"@next/swc-darwin-x64@npm:15.4.0-canary.94":
- version: 15.4.0-canary.94
- resolution: "@next/swc-darwin-x64@npm:15.4.0-canary.94"
+"@next/swc-darwin-x64@npm:15.4.0-canary.95":
+ version: 15.4.0-canary.95
+ resolution: "@next/swc-darwin-x64@npm:15.4.0-canary.95"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
-"@next/swc-linux-arm64-gnu@npm:15.4.0-canary.94":
- version: 15.4.0-canary.94
- resolution: "@next/swc-linux-arm64-gnu@npm:15.4.0-canary.94"
+"@next/swc-linux-arm64-gnu@npm:15.4.0-canary.95":
+ version: 15.4.0-canary.95
+ resolution: "@next/swc-linux-arm64-gnu@npm:15.4.0-canary.95"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
-"@next/swc-linux-arm64-musl@npm:15.4.0-canary.94":
- version: 15.4.0-canary.94
- resolution: "@next/swc-linux-arm64-musl@npm:15.4.0-canary.94"
+"@next/swc-linux-arm64-musl@npm:15.4.0-canary.95":
+ version: 15.4.0-canary.95
+ resolution: "@next/swc-linux-arm64-musl@npm:15.4.0-canary.95"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
-"@next/swc-linux-x64-gnu@npm:15.4.0-canary.94":
- version: 15.4.0-canary.94
- resolution: "@next/swc-linux-x64-gnu@npm:15.4.0-canary.94"
+"@next/swc-linux-x64-gnu@npm:15.4.0-canary.95":
+ version: 15.4.0-canary.95
+ resolution: "@next/swc-linux-x64-gnu@npm:15.4.0-canary.95"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
-"@next/swc-linux-x64-musl@npm:15.4.0-canary.94":
- version: 15.4.0-canary.94
- resolution: "@next/swc-linux-x64-musl@npm:15.4.0-canary.94"
+"@next/swc-linux-x64-musl@npm:15.4.0-canary.95":
+ version: 15.4.0-canary.95
+ resolution: "@next/swc-linux-x64-musl@npm:15.4.0-canary.95"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
-"@next/swc-win32-arm64-msvc@npm:15.4.0-canary.94":
- version: 15.4.0-canary.94
- resolution: "@next/swc-win32-arm64-msvc@npm:15.4.0-canary.94"
+"@next/swc-win32-arm64-msvc@npm:15.4.0-canary.95":
+ version: 15.4.0-canary.95
+ resolution: "@next/swc-win32-arm64-msvc@npm:15.4.0-canary.95"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
-"@next/swc-win32-x64-msvc@npm:15.4.0-canary.94":
- version: 15.4.0-canary.94
- resolution: "@next/swc-win32-x64-msvc@npm:15.4.0-canary.94"
+"@next/swc-win32-x64-msvc@npm:15.4.0-canary.95":
+ version: 15.4.0-canary.95
+ resolution: "@next/swc-win32-x64-msvc@npm:15.4.0-canary.95"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
@@ -6573,7 +6573,7 @@ __metadata:
eslint-config-next: "npm:15.3.4"
eslint-config-prettier: "npm:10.1.5"
lucide-react: "npm:^0.511.0"
- next: "npm:15.4.0-canary.94"
+ next: "npm:15.4.0-canary.95"
next-auth: "npm:^5.0.0-beta.25"
next-themes: "npm:^0.4.6"
orval: "npm:7.10.0"
@@ -6856,19 +6856,19 @@ __metadata:
languageName: node
linkType: hard
-"next@npm:15.4.0-canary.94":
- version: 15.4.0-canary.94
- resolution: "next@npm:15.4.0-canary.94"
+"next@npm:15.4.0-canary.95":
+ version: 15.4.0-canary.95
+ resolution: "next@npm:15.4.0-canary.95"
dependencies:
- "@next/env": "npm:15.4.0-canary.94"
- "@next/swc-darwin-arm64": "npm:15.4.0-canary.94"
- "@next/swc-darwin-x64": "npm:15.4.0-canary.94"
- "@next/swc-linux-arm64-gnu": "npm:15.4.0-canary.94"
- "@next/swc-linux-arm64-musl": "npm:15.4.0-canary.94"
- "@next/swc-linux-x64-gnu": "npm:15.4.0-canary.94"
- "@next/swc-linux-x64-musl": "npm:15.4.0-canary.94"
- "@next/swc-win32-arm64-msvc": "npm:15.4.0-canary.94"
- "@next/swc-win32-x64-msvc": "npm:15.4.0-canary.94"
+ "@next/env": "npm:15.4.0-canary.95"
+ "@next/swc-darwin-arm64": "npm:15.4.0-canary.95"
+ "@next/swc-darwin-x64": "npm:15.4.0-canary.95"
+ "@next/swc-linux-arm64-gnu": "npm:15.4.0-canary.95"
+ "@next/swc-linux-arm64-musl": "npm:15.4.0-canary.95"
+ "@next/swc-linux-x64-gnu": "npm:15.4.0-canary.95"
+ "@next/swc-linux-x64-musl": "npm:15.4.0-canary.95"
+ "@next/swc-win32-arm64-msvc": "npm:15.4.0-canary.95"
+ "@next/swc-win32-x64-msvc": "npm:15.4.0-canary.95"
"@swc/helpers": "npm:0.5.15"
caniuse-lite: "npm:^1.0.30001579"
postcss: "npm:8.4.31"
@@ -6911,7 +6911,7 @@ __metadata:
optional: true
bin:
next: dist/bin/next
- checksum: 10c0/4d3a3f0f85524bb0d595a58b61e017dee584024d4190f7f2709a4dd02ff922040e206531a652d89c3e47447c9c2c2e3b84729fd52fb9db1cb88db6c46c52c537
+ checksum: 10c0/7841ffa1522278ae2205e3343be2ab61bb5d6a1492c062f2469031c59f00dff3bd102d336a224d6956c6d0c57700ff0818d7f9e5ee926a5315ab73ae39062f2e
languageName: node
linkType: hard
From 944d799293cde9f954d8633474e9aeeee38723a5 Mon Sep 17 00:00:00 2001
From: SomeCodecat <88855796+SomeCodecat@users.noreply.github.com>
Date: Wed, 25 Jun 2025 12:01:04 +0200
Subject: [PATCH 12/16] refactor: remove unused imports from notification
button and user dropdown components
---
src/components/buttons/notification-button.tsx | 9 ---------
src/components/custom-ui/app-sidebar.tsx | 14 --------------
src/components/misc/user-dropdown.tsx | 7 -------
3 files changed, 30 deletions(-)
diff --git a/src/components/buttons/notification-button.tsx b/src/components/buttons/notification-button.tsx
index 0b718f9..f41f325 100644
--- a/src/components/buttons/notification-button.tsx
+++ b/src/components/buttons/notification-button.tsx
@@ -2,15 +2,6 @@ import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
- // DropdownMenuGroup,
- // DropdownMenuItem,
- // DropdownMenuLabel,
- // DropdownMenuPortal,
- // DropdownMenuSeparator,
- // DropdownMenuShortcut,
- // DropdownMenuSub,
- // DropdownMenuSubContent,
- // DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { NDot, NotificationDot } from '@/components/misc/notification-dot';
diff --git a/src/components/custom-ui/app-sidebar.tsx b/src/components/custom-ui/app-sidebar.tsx
index 4861363..f823970 100644
--- a/src/components/custom-ui/app-sidebar.tsx
+++ b/src/components/custom-ui/app-sidebar.tsx
@@ -6,26 +6,12 @@ import {
SidebarContent,
SidebarFooter,
SidebarGroup,
- // SidebarGroupAction,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
- // SidebarInput,
- // SidebarInset,
SidebarMenu,
- // SidebarMenuAction,
- // SidebarMenuBadge,
SidebarMenuButton,
SidebarMenuItem,
- // SidebarMenuSkeleton,
- // SidebarMenuSub,
- // SidebarMenuSubButton,
- // SidebarMenuSubItem,
- // SidebarProvider,
- // SidebarRail,
- // SidebarSeparator,
- // SidebarTrigger,
- // useSidebar,
} from '@/components/custom-ui/sidebar';
import { ChevronDown } from 'lucide-react';
diff --git a/src/components/misc/user-dropdown.tsx b/src/components/misc/user-dropdown.tsx
index 8f5aa05..e55f4bb 100644
--- a/src/components/misc/user-dropdown.tsx
+++ b/src/components/misc/user-dropdown.tsx
@@ -5,15 +5,8 @@ import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
- DropdownMenuGroup,
DropdownMenuItem,
- // DropdownMenuLabel,
- // DropdownMenuPortal,
DropdownMenuSeparator,
- // DropdownMenuShortcut,
- // DropdownMenuSub,
- // DropdownMenuSubContent,
- // DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { useGetApiUserMe } from '@/generated/api/user/user';
From a6f74e0c22bb394f4a74d34b930f10f99f24a1ad Mon Sep 17 00:00:00 2001
From: SomeCodecat <88855796+SomeCodecat@users.noreply.github.com>
Date: Mon, 16 Jun 2025 13:01:20 +0200
Subject: [PATCH 13/16] feat: add Radix UI components and implement sidebar
functionality
- Added new Radix UI components: Dialog, Tooltip, Separator, and updated existing components.
- Introduced a Sidebar component with collapsible functionality and mobile responsiveness.
- Implemented a custom hook `useIsMobile` to manage mobile state.
- Updated package dependencies in package.json and yarn.lock for new components.
- Created utility components such as Button, Skeleton, and Input for consistent styling.
feat: add AppSidebar component with collapsible functionality and sidebar menu
- Introduced AppSidebar component for a customizable sidebar layout.
- Implemented collapsible sections using Radix UI's Collapsible component.
- Added sidebar menu items with icons and links for navigation.
- Created Sidebar UI components including SidebarHeader, SidebarFooter, and SidebarMenu.
- Integrated ThemePicker for theme selection within the sidebar.
- Updated sidebar styles and layout for better responsiveness.
chore: add @radix-ui/react-collapsible dependency
- Added @radix-ui/react-collapsible package to manage collapsible UI elements.
---
package.json | 9 +-
src/app/{ => (main)}/home/page.tsx | 4 +-
src/app/(main)/layout.tsx | 23 +
src/app/globals.css | 18 +-
src/components/custom-ui/app-sidebar.tsx | 147 ++++
src/components/misc/header.tsx | 23 +
src/components/misc/logo.tsx | 1 +
src/components/ui/collapsible.tsx | 33 +
src/components/ui/separator.tsx | 7 +-
src/components/ui/sheet.tsx | 139 ++++
src/components/ui/sidebar.tsx | 725 +++++++++++++++++++
src/components/ui/skeleton.tsx | 13 +
src/components/ui/tooltip.tsx | 61 ++
src/components/wrappers/sidebar-provider.tsx | 23 +
src/hooks/use-mobile.ts | 19 +
yarn.lock | 482 +++++++-----
16 files changed, 1520 insertions(+), 207 deletions(-)
rename src/app/{ => (main)}/home/page.tsx (81%)
create mode 100644 src/app/(main)/layout.tsx
create mode 100644 src/components/custom-ui/app-sidebar.tsx
create mode 100644 src/components/misc/header.tsx
create mode 100644 src/components/ui/collapsible.tsx
create mode 100644 src/components/ui/sheet.tsx
create mode 100644 src/components/ui/sidebar.tsx
create mode 100644 src/components/ui/skeleton.tsx
create mode 100644 src/components/ui/tooltip.tsx
create mode 100644 src/components/wrappers/sidebar-provider.tsx
create mode 100644 src/hooks/use-mobile.ts
diff --git a/package.json b/package.json
index f943b16..1623ef9 100644
--- a/package.json
+++ b/package.json
@@ -27,20 +27,23 @@
"@fortawesome/react-fontawesome": "^0.2.2",
"@hookform/resolvers": "^5.0.1",
"@prisma/client": "^6.9.0",
+ "@radix-ui/react-collapsible": "^1.1.11",
+ "@radix-ui/react-dialog": "^1.1.14",
"@radix-ui/react-dropdown-menu": "^2.1.14",
"@radix-ui/react-hover-card": "^1.1.13",
"@radix-ui/react-label": "^2.1.6",
"@radix-ui/react-scroll-area": "^1.2.8",
"@radix-ui/react-select": "^2.2.4",
- "@radix-ui/react-separator": "^1.1.6",
- "@radix-ui/react-slot": "^1.2.2",
+ "@radix-ui/react-separator": "^1.1.7",
+ "@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.2.4",
"@radix-ui/react-tabs": "^1.1.11",
+ "@radix-ui/react-tooltip": "^1.2.7",
"@tanstack/react-query": "^5.80.7",
"bcryptjs": "^3.0.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
- "lucide-react": "^0.511.0",
+ "lucide-react": "^0.515.0",
"next": "15.4.0-canary.95",
"next-auth": "^5.0.0-beta.25",
"next-themes": "^0.4.6",
diff --git a/src/app/home/page.tsx b/src/app/(main)/home/page.tsx
similarity index 81%
rename from src/app/home/page.tsx
rename to src/app/(main)/home/page.tsx
index 77f3cf8..c381c03 100644
--- a/src/app/home/page.tsx
+++ b/src/app/(main)/home/page.tsx
@@ -1,15 +1,13 @@
'use client';
import { RedirectButton } from '@/components/buttons/redirect-button';
-import { ThemePicker } from '@/components/misc/theme-picker';
import { useGetApiUserMe } from '@/generated/api/user/user';
export default function Home() {
const { data, isLoading } = useGetApiUserMe();
return (
-
-
{}
+
Hello{' '}
diff --git a/src/app/(main)/layout.tsx b/src/app/(main)/layout.tsx
new file mode 100644
index 0000000..7106e70
--- /dev/null
+++ b/src/app/(main)/layout.tsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import { cookies } from 'next/headers';
+
+import { AppSidebar } from '@/components/custom-ui/app-sidebar';
+import SidebarProviderWrapper from '@/components/wrappers/sidebar-provider';
+import Header from '@/components/misc/header';
+
+export default async function Layout({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ const cookieStore = await cookies();
+ const defaultOpen = cookieStore.get('sidebar_state')?.value === 'true';
+ return (
+ <>
+
+
+
+
+ >
+ );
+}
diff --git a/src/app/globals.css b/src/app/globals.css
index f85cb2f..a5f7eaf 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -55,6 +55,8 @@
--card: var(--neutral-800);
+ --sidebar-width-icon: 32px;
+
/* ------------------- */
--foreground: oklch(0.13 0.028 261.692);
@@ -95,17 +97,17 @@
--chart-5: oklch(0.769 0.188 70.08);
- --sidebar: oklch(0.985 0.002 247.839);
+ --sidebar: var(--background);
- --sidebar-foreground: oklch(0.13 0.028 261.692);
+ --sidebar-foreground: var(--text);
--sidebar-primary: oklch(0.21 0.034 264.665);
- --sidebar-primary-foreground: oklch(0.985 0.002 247.839);
+ --sidebar-primary-foreground: var(--text);
--sidebar-accent: oklch(0.967 0.003 264.542);
- --sidebar-accent-foreground: oklch(0.21 0.034 264.665);
+ --sidebar-accent-foreground: var(--text);
--sidebar-border: oklch(0.928 0.006 264.531);
@@ -339,17 +341,17 @@
--chart-5: oklch(0.645 0.246 16.439);
- --sidebar: oklch(0.21 0.034 264.665);
+ --sidebar: var(--background);
- --sidebar-foreground: oklch(0.985 0.002 247.839);
+ --sidebar-foreground: var(--text);
--sidebar-primary: oklch(0.488 0.243 264.376);
- --sidebar-primary-foreground: oklch(0.985 0.002 247.839);
+ --sidebar-primary-foreground: var(--text);
--sidebar-accent: oklch(0.278 0.033 256.848);
- --sidebar-accent-foreground: oklch(0.985 0.002 247.839);
+ --sidebar-accent-foreground: var(--text);
--sidebar-border: oklch(1 0 0 / 10%);
diff --git a/src/components/custom-ui/app-sidebar.tsx b/src/components/custom-ui/app-sidebar.tsx
new file mode 100644
index 0000000..b279c73
--- /dev/null
+++ b/src/components/custom-ui/app-sidebar.tsx
@@ -0,0 +1,147 @@
+'use client';
+
+import React from 'react';
+import {
+ Sidebar,
+ SidebarContent,
+ SidebarFooter,
+ SidebarGroup,
+ SidebarGroupAction,
+ SidebarGroupContent,
+ SidebarGroupLabel,
+ SidebarHeader,
+ SidebarInput,
+ SidebarInset,
+ SidebarMenu,
+ SidebarMenuAction,
+ SidebarMenuBadge,
+ SidebarMenuButton,
+ SidebarMenuItem,
+ SidebarMenuSkeleton,
+ SidebarMenuSub,
+ SidebarMenuSubButton,
+ SidebarMenuSubItem,
+ SidebarProvider,
+ SidebarRail,
+ SidebarSeparator,
+ SidebarTrigger,
+ useSidebar,
+} from '@/components/ui/sidebar';
+
+import { ChevronDown } from 'lucide-react';
+import {
+ Collapsible,
+ CollapsibleContent,
+ CollapsibleTrigger,
+} from '@/components/ui/collapsible';
+
+import Logo from '@/components/misc/logo';
+
+import Link from 'next/link';
+
+import { ThemePicker } from '@/components/misc/theme-picker';
+
+import {
+ Star,
+ CalendarDays,
+ User,
+ Users,
+ CalendarClock,
+ CalendarPlus,
+} from 'lucide-react';
+
+const items = [
+ {
+ title: 'Calendar',
+ url: '#',
+ icon: CalendarDays,
+ },
+ {
+ title: 'Friends',
+ url: '#',
+ icon: User,
+ },
+ {
+ title: 'Groups',
+ url: '#',
+ icon: Users,
+ },
+ {
+ title: 'Events',
+ url: '#',
+ icon: CalendarClock,
+ },
+];
+
+export function AppSidebar() {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+ {' '}
+
+ Favorites
+
+
+
+
+
+
+
+
+
+
+
+
+ {items.map((item) => (
+
+
+
+
+
+ {item.title}
+
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+ New Event
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/components/misc/header.tsx b/src/components/misc/header.tsx
new file mode 100644
index 0000000..dbf8a1f
--- /dev/null
+++ b/src/components/misc/header.tsx
@@ -0,0 +1,23 @@
+import { SidebarTrigger } from '@/components/ui/sidebar';
+import { ThemePicker } from '@/components/misc/theme-picker';
+
+export default function Header({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ return (
+
+
+ {children}
+
+ );
+}
diff --git a/src/components/misc/logo.tsx b/src/components/misc/logo.tsx
index 129adef..739fc90 100644
--- a/src/components/misc/logo.tsx
+++ b/src/components/misc/logo.tsx
@@ -90,6 +90,7 @@ export default function Logo({
return (
) {
+ return ;
+}
+
+function CollapsibleTrigger({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function CollapsibleContent({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export { Collapsible, CollapsibleTrigger, CollapsibleContent };
diff --git a/src/components/ui/separator.tsx b/src/components/ui/separator.tsx
index 3b4f1ef..3234cdc 100644
--- a/src/components/ui/separator.tsx
+++ b/src/components/ui/separator.tsx
@@ -13,10 +13,13 @@ function Separator({
}: React.ComponentProps) {
return (
);
diff --git a/src/components/ui/sheet.tsx b/src/components/ui/sheet.tsx
new file mode 100644
index 0000000..84649ad
--- /dev/null
+++ b/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 { XIcon } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+function Sheet({ ...props }: React.ComponentProps) {
+ return
+}
+
+function SheetTrigger({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function SheetClose({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function SheetPortal({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function SheetOverlay({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SheetContent({
+ className,
+ children,
+ side = "right",
+ ...props
+}: React.ComponentProps & {
+ side?: "top" | "right" | "bottom" | "left"
+}) {
+ return (
+
+
+
+ {children}
+
+
+ Close
+
+
+
+ )
+}
+
+function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SheetTitle({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SheetDescription({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export {
+ Sheet,
+ SheetTrigger,
+ SheetClose,
+ SheetContent,
+ SheetHeader,
+ SheetFooter,
+ SheetTitle,
+ SheetDescription,
+}
diff --git a/src/components/ui/sidebar.tsx b/src/components/ui/sidebar.tsx
new file mode 100644
index 0000000..6b68b8f
--- /dev/null
+++ b/src/components/ui/sidebar.tsx
@@ -0,0 +1,725 @@
+'use client';
+
+import * as React from 'react';
+import { Slot } from '@radix-ui/react-slot';
+import { cva, VariantProps } from 'class-variance-authority';
+import { PanelLeftIcon } from 'lucide-react';
+
+import { useIsMobile } from '@/hooks/use-mobile';
+import { cn } from '@/lib/utils';
+import { Button } from '@/components/ui/button';
+import { Input } from '@/components/ui/input';
+import { Separator } from '@/components/ui/separator';
+import {
+ Sheet,
+ SheetContent,
+ SheetDescription,
+ SheetHeader,
+ SheetTitle,
+} from '@/components/ui/sheet';
+import { Skeleton } from '@/components/ui/skeleton';
+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 = '4rem';
+const SIDEBAR_KEYBOARD_SHORTCUT = 'b';
+
+type SidebarContextProps = {
+ state: 'expanded' | 'collapsed';
+ open: boolean;
+ setOpen: (open: boolean) => void;
+ openMobile: boolean;
+ setOpenMobile: (open: boolean) => void;
+ isMobile: boolean;
+ toggleSidebar: () => void;
+};
+
+const SidebarContext = React.createContext(null);
+
+function useSidebar() {
+ const context = React.useContext(SidebarContext);
+ if (!context) {
+ throw new Error('useSidebar must be used within a SidebarProvider.');
+ }
+
+ return context;
+}
+
+function SidebarProvider({
+ defaultOpen = true,
+ open: openProp,
+ onOpenChange: setOpenProp,
+ className,
+ style,
+ children,
+ ...props
+}: React.ComponentProps<'div'> & {
+ defaultOpen?: boolean;
+ open?: boolean;
+ onOpenChange?: (open: boolean) => void;
+}) {
+ 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(
+ () => ({
+ state,
+ open,
+ setOpen,
+ isMobile,
+ openMobile,
+ setOpenMobile,
+ toggleSidebar,
+ }),
+ [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar],
+ );
+
+ return (
+
+
+
+ {children}
+
+
+
+ );
+}
+
+function Sidebar({
+ side = 'left',
+ variant = 'sidebar',
+ collapsible = 'offcanvas',
+ className,
+ children,
+ ...props
+}: React.ComponentProps<'div'> & {
+ side?: 'left' | 'right';
+ variant?: 'sidebar' | 'floating' | 'inset';
+ collapsible?: 'offcanvas' | 'icon' | 'none';
+}) {
+ const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
+
+ if (collapsible === 'none') {
+ return (
+
+ {children}
+
+ );
+ }
+
+ if (isMobile) {
+ return (
+
+
+
+ Sidebar
+ Displays the mobile sidebar.
+
+ {children}
+
+
+ );
+ }
+
+ return (
+
+ {/* This is what handles the sidebar gap on desktop */}
+
+
+
+ );
+}
+
+function SidebarTrigger({
+ className,
+ onClick,
+ ...props
+}: React.ComponentProps) {
+ const { toggleSidebar } = useSidebar();
+
+ return (
+
+ );
+}
+
+function SidebarRail({ className, ...props }: React.ComponentProps<'button'>) {
+ const { toggleSidebar } = useSidebar();
+
+ return (
+
+ );
+}
+
+function SidebarInset({ className, ...props }: React.ComponentProps<'main'>) {
+ return (
+
+ );
+}
+
+function SidebarInput({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function SidebarHeader({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function SidebarFooter({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function SidebarSeparator({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function SidebarContent({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function SidebarGroup({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function SidebarGroupLabel({
+ className,
+ asChild = false,
+ ...props
+}: React.ComponentProps<'div'> & { asChild?: boolean }) {
+ const Comp = asChild ? Slot : 'div';
+
+ return (
+ svg]:size-4 [&>svg]:shrink-0 ml-[7.5px]',
+ className,
+ )}
+ {...props}
+ />
+ );
+}
+
+function SidebarGroupAction({
+ className,
+ asChild = false,
+ ...props
+}: React.ComponentProps<'button'> & { asChild?: boolean }) {
+ const Comp = asChild ? Slot : 'button';
+
+ return (
+ svg]:size-4 [&>svg]:shrink-0',
+ // Increases the hit area of the button on mobile.
+ 'after:absolute after:-inset-2 md:after:hidden',
+ 'group-data-[collapsible=icon]:hidden',
+ className,
+ )}
+ {...props}
+ />
+ );
+}
+
+function SidebarGroupContent({
+ className,
+ ...props
+}: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function SidebarMenu({ className, ...props }: React.ComponentProps<'ul'>) {
+ return (
+
+ );
+}
+
+function SidebarMenuItem({ className, ...props }: React.ComponentProps<'li'>) {
+ return (
+
+ );
+}
+
+const sidebarMenuButtonVariants = cva(
+ 'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden 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! p-0 ml-[15.5px] [&>span:last-child]:truncate [&>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',
+ },
+ },
+);
+
+function SidebarMenuButton({
+ asChild = false,
+ isActive = false,
+ variant = 'default',
+ size = 'default',
+ tooltip,
+ className,
+ ...props
+}: React.ComponentProps<'button'> & {
+ asChild?: boolean;
+ isActive?: boolean;
+ tooltip?: string | React.ComponentProps;
+} & VariantProps) {
+ const Comp = asChild ? Slot : 'button';
+ const { isMobile, state } = useSidebar();
+
+ const button = (
+
+ );
+
+ if (!tooltip) {
+ return button;
+ }
+
+ if (typeof tooltip === 'string') {
+ tooltip = {
+ children: tooltip,
+ };
+ }
+
+ return (
+
+ {button}
+
+
+ );
+}
+
+function SidebarMenuAction({
+ className,
+ asChild = false,
+ showOnHover = false,
+ ...props
+}: React.ComponentProps<'button'> & {
+ asChild?: boolean;
+ showOnHover?: boolean;
+}) {
+ const Comp = asChild ? Slot : 'button';
+
+ return (
+ svg]:size-4 [&>svg]:shrink-0',
+ // Increases the hit area of the button on mobile.
+ 'after:absolute after:-inset-2 md:after: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 &&
+ 'peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0',
+ className,
+ )}
+ {...props}
+ />
+ );
+}
+
+function SidebarMenuBadge({
+ className,
+ ...props
+}: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function SidebarMenuSkeleton({
+ className,
+ showIcon = false,
+ ...props
+}: React.ComponentProps<'div'> & {
+ showIcon?: boolean;
+}) {
+ // Random width between 50 to 90%.
+ const width = React.useMemo(() => {
+ return `${Math.floor(Math.random() * 40) + 50}%`;
+ }, []);
+
+ return (
+
+ {showIcon && (
+
+ )}
+
+
+ );
+}
+
+function SidebarMenuSub({ className, ...props }: React.ComponentProps<'ul'>) {
+ return (
+
+ );
+}
+
+function SidebarMenuSubItem({
+ className,
+ ...props
+}: React.ComponentProps<'li'>) {
+ return (
+
+ );
+}
+
+function SidebarMenuSubButton({
+ asChild = false,
+ size = 'md',
+ isActive = false,
+ className,
+ ...props
+}: React.ComponentProps<'a'> & {
+ asChild?: boolean;
+ size?: 'sm' | 'md';
+ isActive?: boolean;
+}) {
+ const Comp = asChild ? Slot : 'a';
+
+ return (
+ svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-hidden focus-visible:ring-2 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',
+ '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}
+ />
+ );
+}
+
+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,
+};
diff --git a/src/components/ui/skeleton.tsx b/src/components/ui/skeleton.tsx
new file mode 100644
index 0000000..32ea0ef
--- /dev/null
+++ b/src/components/ui/skeleton.tsx
@@ -0,0 +1,13 @@
+import { cn } from "@/lib/utils"
+
+function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+export { Skeleton }
diff --git a/src/components/ui/tooltip.tsx b/src/components/ui/tooltip.tsx
new file mode 100644
index 0000000..4ee26b3
--- /dev/null
+++ b/src/components/ui/tooltip.tsx
@@ -0,0 +1,61 @@
+"use client"
+
+import * as React from "react"
+import * as TooltipPrimitive from "@radix-ui/react-tooltip"
+
+import { cn } from "@/lib/utils"
+
+function TooltipProvider({
+ delayDuration = 0,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function Tooltip({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+function TooltipTrigger({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function TooltipContent({
+ className,
+ sideOffset = 0,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+ {children}
+
+
+
+ )
+}
+
+export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
diff --git a/src/components/wrappers/sidebar-provider.tsx b/src/components/wrappers/sidebar-provider.tsx
new file mode 100644
index 0000000..3873fa4
--- /dev/null
+++ b/src/components/wrappers/sidebar-provider.tsx
@@ -0,0 +1,23 @@
+'use client';
+
+import React from 'react';
+import { SidebarProvider } from '../ui/sidebar';
+
+export default function SidebarProviderWrapper({
+ defaultOpen,
+ children,
+}: {
+ defaultOpen: boolean;
+ children: React.ReactNode;
+}) {
+ const [open, setOpen] = React.useState(defaultOpen);
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/hooks/use-mobile.ts b/src/hooks/use-mobile.ts
new file mode 100644
index 0000000..2b0fe1d
--- /dev/null
+++ b/src/hooks/use-mobile.ts
@@ -0,0 +1,19 @@
+import * as React from "react"
+
+const MOBILE_BREAKPOINT = 768
+
+export function useIsMobile() {
+ const [isMobile, setIsMobile] = React.useState(undefined)
+
+ React.useEffect(() => {
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
+ const onChange = () => {
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
+ }
+ mql.addEventListener("change", onChange)
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
+ return () => mql.removeEventListener("change", onChange)
+ }, [])
+
+ return !!isMobile
+}
diff --git a/yarn.lock b/yarn.lock
index b97a688..3328163 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -543,15 +543,15 @@ __metadata:
linkType: hard
"@gerrit0/mini-shiki@npm:^3.2.2":
- version: 3.6.0
- resolution: "@gerrit0/mini-shiki@npm:3.6.0"
+ version: 3.7.0
+ resolution: "@gerrit0/mini-shiki@npm:3.7.0"
dependencies:
- "@shikijs/engine-oniguruma": "npm:^3.6.0"
- "@shikijs/langs": "npm:^3.6.0"
- "@shikijs/themes": "npm:^3.6.0"
- "@shikijs/types": "npm:^3.6.0"
+ "@shikijs/engine-oniguruma": "npm:^3.7.0"
+ "@shikijs/langs": "npm:^3.7.0"
+ "@shikijs/themes": "npm:^3.7.0"
+ "@shikijs/types": "npm:^3.7.0"
"@shikijs/vscode-textmate": "npm:^10.0.2"
- checksum: 10c0/347456c9da8a1fadd3c1f63097da459a5f930ef4bca6431cce913a379012c551e061d0a94ff7a0f307215b87f2418b7c198a55fba888fc97fb02ab36247adf6b
+ checksum: 10c0/eb3f4900d841338077d839ebbc7f8722b13876a586cff7abc73295e956683724dd3371a9f990900184a2d069461965951b2604d677991badf3474262e7811384
languageName: node
linkType: hard
@@ -1299,6 +1299,32 @@ __metadata:
languageName: node
linkType: hard
+"@radix-ui/react-collapsible@npm:^1.1.11":
+ version: 1.1.11
+ resolution: "@radix-ui/react-collapsible@npm:1.1.11"
+ dependencies:
+ "@radix-ui/primitive": "npm:1.1.2"
+ "@radix-ui/react-compose-refs": "npm:1.1.2"
+ "@radix-ui/react-context": "npm:1.1.2"
+ "@radix-ui/react-id": "npm:1.1.1"
+ "@radix-ui/react-presence": "npm:1.1.4"
+ "@radix-ui/react-primitive": "npm:2.1.3"
+ "@radix-ui/react-use-controllable-state": "npm:1.2.2"
+ "@radix-ui/react-use-layout-effect": "npm:1.1.1"
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+ checksum: 10c0/fa2de539ef06e2b2d18acebb12a34ce1534ca88bd484b7359aac05534d1e551fe83eaafbf60915c00161bb370f0dc9fc303903133510dea0a59fd018155b7db5
+ languageName: node
+ linkType: hard
+
"@radix-ui/react-collection@npm:1.1.7":
version: 1.1.7
resolution: "@radix-ui/react-collection@npm:1.1.7"
@@ -1347,6 +1373,38 @@ __metadata:
languageName: node
linkType: hard
+"@radix-ui/react-dialog@npm:^1.1.14":
+ version: 1.1.14
+ resolution: "@radix-ui/react-dialog@npm:1.1.14"
+ dependencies:
+ "@radix-ui/primitive": "npm:1.1.2"
+ "@radix-ui/react-compose-refs": "npm:1.1.2"
+ "@radix-ui/react-context": "npm:1.1.2"
+ "@radix-ui/react-dismissable-layer": "npm:1.1.10"
+ "@radix-ui/react-focus-guards": "npm:1.1.2"
+ "@radix-ui/react-focus-scope": "npm:1.1.7"
+ "@radix-ui/react-id": "npm:1.1.1"
+ "@radix-ui/react-portal": "npm:1.1.9"
+ "@radix-ui/react-presence": "npm:1.1.4"
+ "@radix-ui/react-primitive": "npm:2.1.3"
+ "@radix-ui/react-slot": "npm:1.2.3"
+ "@radix-ui/react-use-controllable-state": "npm:1.2.2"
+ aria-hidden: "npm:^1.2.4"
+ react-remove-scroll: "npm:^2.6.3"
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+ checksum: 10c0/ab7bc783510ed8fccfe91020b214f4a571d5a1d46d398faa33f4c151bc9f586c47483b307e72b67687b06694c194b3aa80dd1de728460fa765db9f3057690ba3
+ languageName: node
+ linkType: hard
+
"@radix-ui/react-direction@npm:1.1.1":
version: 1.1.1
resolution: "@radix-ui/react-direction@npm:1.1.1"
@@ -1719,7 +1777,7 @@ __metadata:
languageName: node
linkType: hard
-"@radix-ui/react-separator@npm:^1.1.6":
+"@radix-ui/react-separator@npm:^1.1.7":
version: 1.1.7
resolution: "@radix-ui/react-separator@npm:1.1.7"
dependencies:
@@ -1738,7 +1796,7 @@ __metadata:
languageName: node
linkType: hard
-"@radix-ui/react-slot@npm:1.2.3, @radix-ui/react-slot@npm:^1.2.2":
+"@radix-ui/react-slot@npm:1.2.3, @radix-ui/react-slot@npm:^1.2.3":
version: 1.2.3
resolution: "@radix-ui/react-slot@npm:1.2.3"
dependencies:
@@ -1804,6 +1862,36 @@ __metadata:
languageName: node
linkType: hard
+"@radix-ui/react-tooltip@npm:^1.2.7":
+ version: 1.2.7
+ resolution: "@radix-ui/react-tooltip@npm:1.2.7"
+ dependencies:
+ "@radix-ui/primitive": "npm:1.1.2"
+ "@radix-ui/react-compose-refs": "npm:1.1.2"
+ "@radix-ui/react-context": "npm:1.1.2"
+ "@radix-ui/react-dismissable-layer": "npm:1.1.10"
+ "@radix-ui/react-id": "npm:1.1.1"
+ "@radix-ui/react-popper": "npm:1.2.7"
+ "@radix-ui/react-portal": "npm:1.1.9"
+ "@radix-ui/react-presence": "npm:1.1.4"
+ "@radix-ui/react-primitive": "npm:2.1.3"
+ "@radix-ui/react-slot": "npm:1.2.3"
+ "@radix-ui/react-use-controllable-state": "npm:1.2.2"
+ "@radix-ui/react-visually-hidden": "npm:1.2.3"
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+ checksum: 10c0/28798d576c6ffec4f11120cd563aa9d5ab9afb9a37dc18778176442756d026c8c46eec1ddc647b2b5914045495fcb89f82530106e91acb55776b7d6b1a10fb57
+ languageName: node
+ linkType: hard
+
"@radix-ui/react-use-callback-ref@npm:1.1.1":
version: 1.1.1
resolution: "@radix-ui/react-use-callback-ref@npm:1.1.1"
@@ -1966,7 +2054,7 @@ __metadata:
languageName: node
linkType: hard
-"@shikijs/engine-oniguruma@npm:^3.6.0":
+"@shikijs/engine-oniguruma@npm:^3.7.0":
version: 3.7.0
resolution: "@shikijs/engine-oniguruma@npm:3.7.0"
dependencies:
@@ -1976,7 +2064,7 @@ __metadata:
languageName: node
linkType: hard
-"@shikijs/langs@npm:^3.6.0":
+"@shikijs/langs@npm:^3.7.0":
version: 3.7.0
resolution: "@shikijs/langs@npm:3.7.0"
dependencies:
@@ -1985,7 +2073,7 @@ __metadata:
languageName: node
linkType: hard
-"@shikijs/themes@npm:^3.6.0":
+"@shikijs/themes@npm:^3.7.0":
version: 3.7.0
resolution: "@shikijs/themes@npm:3.7.0"
dependencies:
@@ -1994,7 +2082,7 @@ __metadata:
languageName: node
linkType: hard
-"@shikijs/types@npm:3.7.0, @shikijs/types@npm:^3.6.0":
+"@shikijs/types@npm:3.7.0, @shikijs/types@npm:^3.7.0":
version: 3.7.0
resolution: "@shikijs/types@npm:3.7.0"
dependencies:
@@ -3094,11 +3182,11 @@ __metadata:
linkType: hard
"@types/node@npm:*":
- version: 24.0.3
- resolution: "@types/node@npm:24.0.3"
+ version: 24.0.4
+ resolution: "@types/node@npm:24.0.4"
dependencies:
undici-types: "npm:~7.8.0"
- checksum: 10c0/9c3c4e87600d1cf11e291c2fd4bfd806a615455463c30a0ef6dc9c801b3423344d9b82b8084e3ccabce485a7421ebb61a66e9676181bd7d9aea4759998a120d5
+ checksum: 10c0/590e8cb0ec59fb9cd566402120e690d87ecbdf57f1ee2b8493266121ed33aa4b25949a0c6156b84a6ffb9250baaf1f80e9af142da542ed603e6ee73fc4d1115f
languageName: node
linkType: hard
@@ -3190,104 +3278,104 @@ __metadata:
linkType: hard
"@typescript-eslint/eslint-plugin@npm:^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0":
- version: 8.34.1
- resolution: "@typescript-eslint/eslint-plugin@npm:8.34.1"
+ version: 8.35.0
+ resolution: "@typescript-eslint/eslint-plugin@npm:8.35.0"
dependencies:
"@eslint-community/regexpp": "npm:^4.10.0"
- "@typescript-eslint/scope-manager": "npm:8.34.1"
- "@typescript-eslint/type-utils": "npm:8.34.1"
- "@typescript-eslint/utils": "npm:8.34.1"
- "@typescript-eslint/visitor-keys": "npm:8.34.1"
+ "@typescript-eslint/scope-manager": "npm:8.35.0"
+ "@typescript-eslint/type-utils": "npm:8.35.0"
+ "@typescript-eslint/utils": "npm:8.35.0"
+ "@typescript-eslint/visitor-keys": "npm:8.35.0"
graphemer: "npm:^1.4.0"
ignore: "npm:^7.0.0"
natural-compare: "npm:^1.4.0"
ts-api-utils: "npm:^2.1.0"
peerDependencies:
- "@typescript-eslint/parser": ^8.34.1
+ "@typescript-eslint/parser": ^8.35.0
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <5.9.0"
- checksum: 10c0/f1c9f25e4fe4b59622312dfa0ca1e80fa7945296ba5c04362a5fda084a17e23a6b98dac331f5a13bcb1ba34a2b598a3f5c41aa288f0c51fe60196e912954e56a
+ checksum: 10c0/27391f1b168a175fdc62370e5afe51317d4433115abbbff8ee0aea8ecd7bf6dd541a76f8e0cc94119750ae3146863204862640acb45394f0b92809e88d39f881
languageName: node
linkType: hard
"@typescript-eslint/parser@npm:^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0":
- version: 8.34.1
- resolution: "@typescript-eslint/parser@npm:8.34.1"
+ version: 8.35.0
+ resolution: "@typescript-eslint/parser@npm:8.35.0"
dependencies:
- "@typescript-eslint/scope-manager": "npm:8.34.1"
- "@typescript-eslint/types": "npm:8.34.1"
- "@typescript-eslint/typescript-estree": "npm:8.34.1"
- "@typescript-eslint/visitor-keys": "npm:8.34.1"
+ "@typescript-eslint/scope-manager": "npm:8.35.0"
+ "@typescript-eslint/types": "npm:8.35.0"
+ "@typescript-eslint/typescript-estree": "npm:8.35.0"
+ "@typescript-eslint/visitor-keys": "npm:8.35.0"
debug: "npm:^4.3.4"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <5.9.0"
- checksum: 10c0/bf8070245d53ef6926ff6630bb72f245923f545304e2a61508fb944802a83fed8eab961d9010956d07999d51afdfbbec82aea9d6185295551a7c17c00d759183
+ checksum: 10c0/8f1cda98f8bee3d79266974e5e5c831a0ca473e928fb16f1dc1c85ee24f2cb9c0fcf3c1bcbbef9d6044cf063f6e59d3198b766a27000776830fe591043e11625
languageName: node
linkType: hard
-"@typescript-eslint/project-service@npm:8.34.1":
- version: 8.34.1
- resolution: "@typescript-eslint/project-service@npm:8.34.1"
+"@typescript-eslint/project-service@npm:8.35.0":
+ version: 8.35.0
+ resolution: "@typescript-eslint/project-service@npm:8.35.0"
dependencies:
- "@typescript-eslint/tsconfig-utils": "npm:^8.34.1"
- "@typescript-eslint/types": "npm:^8.34.1"
+ "@typescript-eslint/tsconfig-utils": "npm:^8.35.0"
+ "@typescript-eslint/types": "npm:^8.35.0"
debug: "npm:^4.3.4"
peerDependencies:
typescript: ">=4.8.4 <5.9.0"
- checksum: 10c0/9333a890625f6777054db17a6b299281ae7502bb7615261d15b885a75b8cf65fc91591389c93b37ecd14b651d8e94851dac8718e5dcc8ed0600533535dae855c
+ checksum: 10c0/c2d6d44b6b2ff3ecabec8ade824163196799060ac457661eb94049487d770ce68d128b33a2f24090adf1ebcb66ff6c9a05fc6659349b9a0784a5a080ecf8ff81
languageName: node
linkType: hard
-"@typescript-eslint/scope-manager@npm:8.34.1":
- version: 8.34.1
- resolution: "@typescript-eslint/scope-manager@npm:8.34.1"
+"@typescript-eslint/scope-manager@npm:8.35.0":
+ version: 8.35.0
+ resolution: "@typescript-eslint/scope-manager@npm:8.35.0"
dependencies:
- "@typescript-eslint/types": "npm:8.34.1"
- "@typescript-eslint/visitor-keys": "npm:8.34.1"
- checksum: 10c0/2af608fa3900f4726322e33bf4f3a376fdace3ac0f310cf7d9256bbc2905c3896138176a47dd195d2c2229f27fe43f5deb4bc7729db2eb18389926dedea78077
+ "@typescript-eslint/types": "npm:8.35.0"
+ "@typescript-eslint/visitor-keys": "npm:8.35.0"
+ checksum: 10c0/a27cf27a1852bb0d6ea08f475fcc79557f1977be96ef563d92127e8011e4065566441c32c40eb7a530111ffd3a8489919da7f8a2b7466a610cfc9c07670a9601
languageName: node
linkType: hard
-"@typescript-eslint/tsconfig-utils@npm:8.34.1, @typescript-eslint/tsconfig-utils@npm:^8.34.1":
- version: 8.34.1
- resolution: "@typescript-eslint/tsconfig-utils@npm:8.34.1"
+"@typescript-eslint/tsconfig-utils@npm:8.35.0, @typescript-eslint/tsconfig-utils@npm:^8.35.0":
+ version: 8.35.0
+ resolution: "@typescript-eslint/tsconfig-utils@npm:8.35.0"
peerDependencies:
typescript: ">=4.8.4 <5.9.0"
- checksum: 10c0/8d1ead8b7c279b48e2ed96f083ec119a9aeea1ca9cdd40576ec271b996b9fd8cfa0ddb0aafbb4e14bc27fc62c69c5be66d39b1de68eab9ddd7f1861da267423d
+ checksum: 10c0/baa18e7137ba72f7d138f50d1168e8f334198a36499f954821e2369027e5b3d53ca93c354943e7782ba5caab604b050af10f353ccca34fbc0b23c48d6174832f
languageName: node
linkType: hard
-"@typescript-eslint/type-utils@npm:8.34.1":
- version: 8.34.1
- resolution: "@typescript-eslint/type-utils@npm:8.34.1"
+"@typescript-eslint/type-utils@npm:8.35.0":
+ version: 8.35.0
+ resolution: "@typescript-eslint/type-utils@npm:8.35.0"
dependencies:
- "@typescript-eslint/typescript-estree": "npm:8.34.1"
- "@typescript-eslint/utils": "npm:8.34.1"
+ "@typescript-eslint/typescript-estree": "npm:8.35.0"
+ "@typescript-eslint/utils": "npm:8.35.0"
debug: "npm:^4.3.4"
ts-api-utils: "npm:^2.1.0"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <5.9.0"
- checksum: 10c0/502a2cdfe47f1f34206c747b5a70e0242dd99f570511db3dda9c5f999d9abadfbbb1dfa82a1fa437a1689d232715412e61c97d95f19c9314ba5ad23196b4096d
+ checksum: 10c0/9e23a332484a055eb73ba8918f95a981e0cec8fa623ba9ee0b57328af052628d630a415e32e0dbe95318574e62d4066f8aecc994728b3cedd906f36c616ec362
languageName: node
linkType: hard
-"@typescript-eslint/types@npm:8.34.1, @typescript-eslint/types@npm:^8.34.1":
- version: 8.34.1
- resolution: "@typescript-eslint/types@npm:8.34.1"
- checksum: 10c0/db1b3dce6a70b28ddb13c76fbb5983240d9395656df5f7cbd99bfd9905e39c0dab2132870f01dbc406b48739c437f7d344a879a824cedaba81b91a53110dc23a
+"@typescript-eslint/types@npm:8.35.0, @typescript-eslint/types@npm:^8.35.0":
+ version: 8.35.0
+ resolution: "@typescript-eslint/types@npm:8.35.0"
+ checksum: 10c0/a2711a932680805e83252b5d7c55ac30437bdc4d40c444606cf6ccb6ba23a682da015ec03c64635e77bf733f84d9bb76810bf4f7177fd3a660db8a2c8a05e845
languageName: node
linkType: hard
-"@typescript-eslint/typescript-estree@npm:8.34.1":
- version: 8.34.1
- resolution: "@typescript-eslint/typescript-estree@npm:8.34.1"
+"@typescript-eslint/typescript-estree@npm:8.35.0":
+ version: 8.35.0
+ resolution: "@typescript-eslint/typescript-estree@npm:8.35.0"
dependencies:
- "@typescript-eslint/project-service": "npm:8.34.1"
- "@typescript-eslint/tsconfig-utils": "npm:8.34.1"
- "@typescript-eslint/types": "npm:8.34.1"
- "@typescript-eslint/visitor-keys": "npm:8.34.1"
+ "@typescript-eslint/project-service": "npm:8.35.0"
+ "@typescript-eslint/tsconfig-utils": "npm:8.35.0"
+ "@typescript-eslint/types": "npm:8.35.0"
+ "@typescript-eslint/visitor-keys": "npm:8.35.0"
debug: "npm:^4.3.4"
fast-glob: "npm:^3.3.2"
is-glob: "npm:^4.0.3"
@@ -3296,166 +3384,166 @@ __metadata:
ts-api-utils: "npm:^2.1.0"
peerDependencies:
typescript: ">=4.8.4 <5.9.0"
- checksum: 10c0/4ee7249db91b9840361f34f80b7b6d646a3af159c7298d79a33d8a11c98792fd3a395343e5e17e0fa29529e8f0113bac8baadcef90d1e140bd736a48f0485042
+ checksum: 10c0/7e94f6a92efc5832289e8bfd0b61209aa501224c935359253c29aeef8e0b981b370ee2a43e2909991c3c3cf709fcccb6380474e0e9a863e8f89e2fbd213aed59
languageName: node
linkType: hard
-"@typescript-eslint/utils@npm:8.34.1":
- version: 8.34.1
- resolution: "@typescript-eslint/utils@npm:8.34.1"
+"@typescript-eslint/utils@npm:8.35.0":
+ version: 8.35.0
+ resolution: "@typescript-eslint/utils@npm:8.35.0"
dependencies:
"@eslint-community/eslint-utils": "npm:^4.7.0"
- "@typescript-eslint/scope-manager": "npm:8.34.1"
- "@typescript-eslint/types": "npm:8.34.1"
- "@typescript-eslint/typescript-estree": "npm:8.34.1"
+ "@typescript-eslint/scope-manager": "npm:8.35.0"
+ "@typescript-eslint/types": "npm:8.35.0"
+ "@typescript-eslint/typescript-estree": "npm:8.35.0"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <5.9.0"
- checksum: 10c0/e3085877f7940c02a37653e6bc52ac6cde115e755b1f788fe4331202f371b3421cc4d0878c7d3eb054e14e9b3a064496a707a73eac471cb2b73593b9e9d4b998
+ checksum: 10c0/e3317df7875305bee16edd573e4bfdafc099f26f9c284d8adb351333683aacd5b668320870653dff7ec7e0da1982bbf89dc06197bc193a3be65362f21452dbea
languageName: node
linkType: hard
-"@typescript-eslint/visitor-keys@npm:8.34.1":
- version: 8.34.1
- resolution: "@typescript-eslint/visitor-keys@npm:8.34.1"
+"@typescript-eslint/visitor-keys@npm:8.35.0":
+ version: 8.35.0
+ resolution: "@typescript-eslint/visitor-keys@npm:8.35.0"
dependencies:
- "@typescript-eslint/types": "npm:8.34.1"
+ "@typescript-eslint/types": "npm:8.35.0"
eslint-visitor-keys: "npm:^4.2.1"
- checksum: 10c0/0e5a9b3d93905d16d3cf8cb5fb346dcc6f760482eb7d0ac209aefc09a32f78ef28a687634df6ad08e81fb3e1083e8805f34472de6bbc501c0105ad654d518f40
+ checksum: 10c0/df18ca9b6931cb58f5dc404fcc94f9e0cc1c22f3053c7013ab588bb8ccccd3d58a70c577c01267845d57fa124a8cf8371260d284dad97505c56b2abcf70a3dce
languageName: node
linkType: hard
-"@unrs/resolver-binding-android-arm-eabi@npm:1.9.1":
- version: 1.9.1
- resolution: "@unrs/resolver-binding-android-arm-eabi@npm:1.9.1"
+"@unrs/resolver-binding-android-arm-eabi@npm:1.9.2":
+ version: 1.9.2
+ resolution: "@unrs/resolver-binding-android-arm-eabi@npm:1.9.2"
conditions: os=android & cpu=arm
languageName: node
linkType: hard
-"@unrs/resolver-binding-android-arm64@npm:1.9.1":
- version: 1.9.1
- resolution: "@unrs/resolver-binding-android-arm64@npm:1.9.1"
+"@unrs/resolver-binding-android-arm64@npm:1.9.2":
+ version: 1.9.2
+ resolution: "@unrs/resolver-binding-android-arm64@npm:1.9.2"
conditions: os=android & cpu=arm64
languageName: node
linkType: hard
-"@unrs/resolver-binding-darwin-arm64@npm:1.9.1":
- version: 1.9.1
- resolution: "@unrs/resolver-binding-darwin-arm64@npm:1.9.1"
+"@unrs/resolver-binding-darwin-arm64@npm:1.9.2":
+ version: 1.9.2
+ resolution: "@unrs/resolver-binding-darwin-arm64@npm:1.9.2"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
-"@unrs/resolver-binding-darwin-x64@npm:1.9.1":
- version: 1.9.1
- resolution: "@unrs/resolver-binding-darwin-x64@npm:1.9.1"
+"@unrs/resolver-binding-darwin-x64@npm:1.9.2":
+ version: 1.9.2
+ resolution: "@unrs/resolver-binding-darwin-x64@npm:1.9.2"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
-"@unrs/resolver-binding-freebsd-x64@npm:1.9.1":
- version: 1.9.1
- resolution: "@unrs/resolver-binding-freebsd-x64@npm:1.9.1"
+"@unrs/resolver-binding-freebsd-x64@npm:1.9.2":
+ version: 1.9.2
+ resolution: "@unrs/resolver-binding-freebsd-x64@npm:1.9.2"
conditions: os=freebsd & cpu=x64
languageName: node
linkType: hard
-"@unrs/resolver-binding-linux-arm-gnueabihf@npm:1.9.1":
- version: 1.9.1
- resolution: "@unrs/resolver-binding-linux-arm-gnueabihf@npm:1.9.1"
+"@unrs/resolver-binding-linux-arm-gnueabihf@npm:1.9.2":
+ version: 1.9.2
+ resolution: "@unrs/resolver-binding-linux-arm-gnueabihf@npm:1.9.2"
conditions: os=linux & cpu=arm
languageName: node
linkType: hard
-"@unrs/resolver-binding-linux-arm-musleabihf@npm:1.9.1":
- version: 1.9.1
- resolution: "@unrs/resolver-binding-linux-arm-musleabihf@npm:1.9.1"
+"@unrs/resolver-binding-linux-arm-musleabihf@npm:1.9.2":
+ version: 1.9.2
+ resolution: "@unrs/resolver-binding-linux-arm-musleabihf@npm:1.9.2"
conditions: os=linux & cpu=arm
languageName: node
linkType: hard
-"@unrs/resolver-binding-linux-arm64-gnu@npm:1.9.1":
- version: 1.9.1
- resolution: "@unrs/resolver-binding-linux-arm64-gnu@npm:1.9.1"
+"@unrs/resolver-binding-linux-arm64-gnu@npm:1.9.2":
+ version: 1.9.2
+ resolution: "@unrs/resolver-binding-linux-arm64-gnu@npm:1.9.2"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
-"@unrs/resolver-binding-linux-arm64-musl@npm:1.9.1":
- version: 1.9.1
- resolution: "@unrs/resolver-binding-linux-arm64-musl@npm:1.9.1"
+"@unrs/resolver-binding-linux-arm64-musl@npm:1.9.2":
+ version: 1.9.2
+ resolution: "@unrs/resolver-binding-linux-arm64-musl@npm:1.9.2"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
-"@unrs/resolver-binding-linux-ppc64-gnu@npm:1.9.1":
- version: 1.9.1
- resolution: "@unrs/resolver-binding-linux-ppc64-gnu@npm:1.9.1"
+"@unrs/resolver-binding-linux-ppc64-gnu@npm:1.9.2":
+ version: 1.9.2
+ resolution: "@unrs/resolver-binding-linux-ppc64-gnu@npm:1.9.2"
conditions: os=linux & cpu=ppc64 & libc=glibc
languageName: node
linkType: hard
-"@unrs/resolver-binding-linux-riscv64-gnu@npm:1.9.1":
- version: 1.9.1
- resolution: "@unrs/resolver-binding-linux-riscv64-gnu@npm:1.9.1"
+"@unrs/resolver-binding-linux-riscv64-gnu@npm:1.9.2":
+ version: 1.9.2
+ resolution: "@unrs/resolver-binding-linux-riscv64-gnu@npm:1.9.2"
conditions: os=linux & cpu=riscv64 & libc=glibc
languageName: node
linkType: hard
-"@unrs/resolver-binding-linux-riscv64-musl@npm:1.9.1":
- version: 1.9.1
- resolution: "@unrs/resolver-binding-linux-riscv64-musl@npm:1.9.1"
+"@unrs/resolver-binding-linux-riscv64-musl@npm:1.9.2":
+ version: 1.9.2
+ resolution: "@unrs/resolver-binding-linux-riscv64-musl@npm:1.9.2"
conditions: os=linux & cpu=riscv64 & libc=musl
languageName: node
linkType: hard
-"@unrs/resolver-binding-linux-s390x-gnu@npm:1.9.1":
- version: 1.9.1
- resolution: "@unrs/resolver-binding-linux-s390x-gnu@npm:1.9.1"
+"@unrs/resolver-binding-linux-s390x-gnu@npm:1.9.2":
+ version: 1.9.2
+ resolution: "@unrs/resolver-binding-linux-s390x-gnu@npm:1.9.2"
conditions: os=linux & cpu=s390x & libc=glibc
languageName: node
linkType: hard
-"@unrs/resolver-binding-linux-x64-gnu@npm:1.9.1":
- version: 1.9.1
- resolution: "@unrs/resolver-binding-linux-x64-gnu@npm:1.9.1"
+"@unrs/resolver-binding-linux-x64-gnu@npm:1.9.2":
+ version: 1.9.2
+ resolution: "@unrs/resolver-binding-linux-x64-gnu@npm:1.9.2"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
-"@unrs/resolver-binding-linux-x64-musl@npm:1.9.1":
- version: 1.9.1
- resolution: "@unrs/resolver-binding-linux-x64-musl@npm:1.9.1"
+"@unrs/resolver-binding-linux-x64-musl@npm:1.9.2":
+ version: 1.9.2
+ resolution: "@unrs/resolver-binding-linux-x64-musl@npm:1.9.2"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
-"@unrs/resolver-binding-wasm32-wasi@npm:1.9.1":
- version: 1.9.1
- resolution: "@unrs/resolver-binding-wasm32-wasi@npm:1.9.1"
+"@unrs/resolver-binding-wasm32-wasi@npm:1.9.2":
+ version: 1.9.2
+ resolution: "@unrs/resolver-binding-wasm32-wasi@npm:1.9.2"
dependencies:
"@napi-rs/wasm-runtime": "npm:^0.2.11"
conditions: cpu=wasm32
languageName: node
linkType: hard
-"@unrs/resolver-binding-win32-arm64-msvc@npm:1.9.1":
- version: 1.9.1
- resolution: "@unrs/resolver-binding-win32-arm64-msvc@npm:1.9.1"
+"@unrs/resolver-binding-win32-arm64-msvc@npm:1.9.2":
+ version: 1.9.2
+ resolution: "@unrs/resolver-binding-win32-arm64-msvc@npm:1.9.2"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
-"@unrs/resolver-binding-win32-ia32-msvc@npm:1.9.1":
- version: 1.9.1
- resolution: "@unrs/resolver-binding-win32-ia32-msvc@npm:1.9.1"
+"@unrs/resolver-binding-win32-ia32-msvc@npm:1.9.2":
+ version: 1.9.2
+ resolution: "@unrs/resolver-binding-win32-ia32-msvc@npm:1.9.2"
conditions: os=win32 & cpu=ia32
languageName: node
linkType: hard
-"@unrs/resolver-binding-win32-x64-msvc@npm:1.9.1":
- version: 1.9.1
- resolution: "@unrs/resolver-binding-win32-x64-msvc@npm:1.9.1"
+"@unrs/resolver-binding-win32-x64-msvc@npm:1.9.2":
+ version: 1.9.2
+ resolution: "@unrs/resolver-binding-win32-x64-msvc@npm:1.9.2"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
@@ -3662,7 +3750,7 @@ __metadata:
languageName: node
linkType: hard
-"array-includes@npm:^3.1.6, array-includes@npm:^3.1.8":
+"array-includes@npm:^3.1.6, array-includes@npm:^3.1.8, array-includes@npm:^3.1.9":
version: 3.1.9
resolution: "array-includes@npm:3.1.9"
dependencies:
@@ -3699,7 +3787,7 @@ __metadata:
languageName: node
linkType: hard
-"array.prototype.findlastindex@npm:^1.2.5":
+"array.prototype.findlastindex@npm:^1.2.6":
version: 1.2.6
resolution: "array.prototype.findlastindex@npm:1.2.6"
dependencies:
@@ -3714,7 +3802,7 @@ __metadata:
languageName: node
linkType: hard
-"array.prototype.flat@npm:^1.3.1, array.prototype.flat@npm:^1.3.2":
+"array.prototype.flat@npm:^1.3.1, array.prototype.flat@npm:^1.3.3":
version: 1.3.3
resolution: "array.prototype.flat@npm:1.3.3"
dependencies:
@@ -3964,9 +4052,9 @@ __metadata:
linkType: hard
"caniuse-lite@npm:^1.0.30001579":
- version: 1.0.30001724
- resolution: "caniuse-lite@npm:1.0.30001724"
- checksum: 10c0/ed9ec0bcf619f0e7ef2d33aac74d2346d1faf52060dfded1fb9c32d87854de5c2988b3ba338c281034c88bf797d6b55468a804ce8396a7e16a48cb0d481d4bfe
+ version: 1.0.30001726
+ resolution: "caniuse-lite@npm:1.0.30001726"
+ checksum: 10c0/2c5f91da7fd9ebf8c6b432818b1498ea28aca8de22b30dafabe2a2a6da1e014f10e67e14f8e68e872a0867b6b4cd6001558dde04e3ab9770c9252ca5c8849d0e
languageName: node
linkType: hard
@@ -4418,12 +4506,12 @@ __metadata:
linkType: hard
"enhanced-resolve@npm:^5.18.1":
- version: 5.18.1
- resolution: "enhanced-resolve@npm:5.18.1"
+ version: 5.18.2
+ resolution: "enhanced-resolve@npm:5.18.2"
dependencies:
graceful-fs: "npm:^4.2.4"
tapable: "npm:^2.2.0"
- checksum: 10c0/4cffd9b125225184e2abed9fdf0ed3dbd2224c873b165d0838fd066cde32e0918626cba2f1f4bf6860762f13a7e2364fd89a82b99566be2873d813573ac71846
+ checksum: 10c0/2a45105daded694304b0298d1c0351a981842249a9867513d55e41321a4ccf37dfd35b0c1e9ceae290eab73654b09aa7a910d618ea6f9441e97c52bc424a2372
languageName: node
linkType: hard
@@ -4792,7 +4880,7 @@ __metadata:
languageName: node
linkType: hard
-"eslint-module-utils@npm:^2.12.0":
+"eslint-module-utils@npm:^2.12.1":
version: 2.12.1
resolution: "eslint-module-utils@npm:2.12.1"
dependencies:
@@ -4805,31 +4893,31 @@ __metadata:
linkType: hard
"eslint-plugin-import@npm:^2.31.0":
- version: 2.31.0
- resolution: "eslint-plugin-import@npm:2.31.0"
+ version: 2.32.0
+ resolution: "eslint-plugin-import@npm:2.32.0"
dependencies:
"@rtsao/scc": "npm:^1.1.0"
- array-includes: "npm:^3.1.8"
- array.prototype.findlastindex: "npm:^1.2.5"
- array.prototype.flat: "npm:^1.3.2"
- array.prototype.flatmap: "npm:^1.3.2"
+ array-includes: "npm:^3.1.9"
+ array.prototype.findlastindex: "npm:^1.2.6"
+ array.prototype.flat: "npm:^1.3.3"
+ array.prototype.flatmap: "npm:^1.3.3"
debug: "npm:^3.2.7"
doctrine: "npm:^2.1.0"
eslint-import-resolver-node: "npm:^0.3.9"
- eslint-module-utils: "npm:^2.12.0"
+ eslint-module-utils: "npm:^2.12.1"
hasown: "npm:^2.0.2"
- is-core-module: "npm:^2.15.1"
+ is-core-module: "npm:^2.16.1"
is-glob: "npm:^4.0.3"
minimatch: "npm:^3.1.2"
object.fromentries: "npm:^2.0.8"
object.groupby: "npm:^1.0.3"
- object.values: "npm:^1.2.0"
+ object.values: "npm:^1.2.1"
semver: "npm:^6.3.1"
- string.prototype.trimend: "npm:^1.0.8"
+ string.prototype.trimend: "npm:^1.0.9"
tsconfig-paths: "npm:^3.15.0"
peerDependencies:
eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9
- checksum: 10c0/e21d116ddd1900e091ad120b3eb68c5dd5437fe2c930f1211781cd38b246f090a6b74d5f3800b8255a0ed29782591521ad44eb21c5534960a8f1fb4040fd913a
+ checksum: 10c0/bfb1b8fc8800398e62ddfefbf3638d185286edfed26dfe00875cc2846d954491b4f5112457831588b757fa789384e1ae585f812614c4797f0499fa234fd4a48b
languageName: node
linkType: hard
@@ -5758,7 +5846,7 @@ __metadata:
languageName: node
linkType: hard
-"is-core-module@npm:^2.13.0, is-core-module@npm:^2.15.1, is-core-module@npm:^2.16.0":
+"is-core-module@npm:^2.13.0, is-core-module@npm:^2.16.0, is-core-module@npm:^2.16.1":
version: 2.16.1
resolution: "is-core-module@npm:2.16.1"
dependencies:
@@ -6454,12 +6542,12 @@ __metadata:
languageName: node
linkType: hard
-"lucide-react@npm:^0.511.0":
- version: 0.511.0
- resolution: "lucide-react@npm:0.511.0"
+"lucide-react@npm:^0.515.0":
+ version: 0.515.0
+ resolution: "lucide-react@npm:0.515.0"
peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
- checksum: 10c0/bf09dd73cf2233abea90506ad31a91739555d761062722acbe045cb73e274f035b196472de0971a8a8f0645b2b54e3f21b8c1980fe87c909ca93171a9c28428a
+ checksum: 10c0/00485e09ab3d0bbb34797b1f368c269e8708522b6e2f46fd84dd5bd99741546487be9a65a260f274e8049b81cc37687566e26132f5752352c8d9bc8e5d0b3dea
languageName: node
linkType: hard
@@ -6549,15 +6637,18 @@ __metadata:
"@fortawesome/react-fontawesome": "npm:^0.2.2"
"@hookform/resolvers": "npm:^5.0.1"
"@prisma/client": "npm:^6.9.0"
+ "@radix-ui/react-collapsible": "npm:^1.1.11"
+ "@radix-ui/react-dialog": "npm:^1.1.14"
"@radix-ui/react-dropdown-menu": "npm:^2.1.14"
"@radix-ui/react-hover-card": "npm:^1.1.13"
"@radix-ui/react-label": "npm:^2.1.6"
"@radix-ui/react-scroll-area": "npm:^1.2.8"
"@radix-ui/react-select": "npm:^2.2.4"
- "@radix-ui/react-separator": "npm:^1.1.6"
- "@radix-ui/react-slot": "npm:^1.2.2"
+ "@radix-ui/react-separator": "npm:^1.1.7"
+ "@radix-ui/react-slot": "npm:^1.2.3"
"@radix-ui/react-switch": "npm:^1.2.4"
"@radix-ui/react-tabs": "npm:^1.1.11"
+ "@radix-ui/react-tooltip": "npm:^1.2.7"
"@tailwindcss/postcss": "npm:4.1.10"
"@tanstack/react-query": "npm:^5.80.7"
"@types/node": "npm:22.15.33"
@@ -6572,7 +6663,7 @@ __metadata:
eslint: "npm:9.29.0"
eslint-config-next: "npm:15.3.4"
eslint-config-prettier: "npm:10.1.5"
- lucide-react: "npm:^0.511.0"
+ lucide-react: "npm:^0.515.0"
next: "npm:15.4.0-canary.95"
next-auth: "npm:^5.0.0-beta.25"
next-themes: "npm:^0.4.6"
@@ -6794,7 +6885,7 @@ __metadata:
languageName: node
linkType: hard
-"napi-postinstall@npm:^0.2.2":
+"napi-postinstall@npm:^0.2.4":
version: 0.2.4
resolution: "napi-postinstall@npm:0.2.4"
bin:
@@ -7185,7 +7276,7 @@ __metadata:
languageName: node
linkType: hard
-"object.values@npm:^1.1.6, object.values@npm:^1.2.0, object.values@npm:^1.2.1":
+"object.values@npm:^1.1.6, object.values@npm:^1.2.1":
version: 1.2.1
resolution: "object.values@npm:1.2.1"
dependencies:
@@ -7233,7 +7324,7 @@ __metadata:
languageName: node
linkType: hard
-"openapi3-ts@npm:4.4.0, openapi3-ts@npm:^4.1.2, openapi3-ts@npm:^4.2.2":
+"openapi3-ts@npm:4.4.0":
version: 4.4.0
resolution: "openapi3-ts@npm:4.4.0"
dependencies:
@@ -7242,6 +7333,15 @@ __metadata:
languageName: node
linkType: hard
+"openapi3-ts@npm:^4.1.2, openapi3-ts@npm:^4.2.2":
+ version: 4.5.0
+ resolution: "openapi3-ts@npm:4.5.0"
+ dependencies:
+ yaml: "npm:^2.8.0"
+ checksum: 10c0/97de2d24e9ceffb89e1388e137e4a6e17ee57a02dce0c938a5e98b1338ac72b31e8b2ce8dd28945ad43fae8bee2a145892cb548ba5ae60b0930f1b6b79b0747d
+ languageName: node
+ linkType: hard
+
"optionator@npm:^0.9.3":
version: 0.9.4
resolution: "optionator@npm:0.9.4"
@@ -8569,7 +8669,7 @@ __metadata:
languageName: node
linkType: hard
-"string.prototype.trimend@npm:^1.0.8, string.prototype.trimend@npm:^1.0.9":
+"string.prototype.trimend@npm:^1.0.9":
version: 1.0.9
resolution: "string.prototype.trimend@npm:1.0.9"
dependencies:
@@ -9171,29 +9271,29 @@ __metadata:
linkType: hard
"unrs-resolver@npm:^1.6.2":
- version: 1.9.1
- resolution: "unrs-resolver@npm:1.9.1"
+ version: 1.9.2
+ resolution: "unrs-resolver@npm:1.9.2"
dependencies:
- "@unrs/resolver-binding-android-arm-eabi": "npm:1.9.1"
- "@unrs/resolver-binding-android-arm64": "npm:1.9.1"
- "@unrs/resolver-binding-darwin-arm64": "npm:1.9.1"
- "@unrs/resolver-binding-darwin-x64": "npm:1.9.1"
- "@unrs/resolver-binding-freebsd-x64": "npm:1.9.1"
- "@unrs/resolver-binding-linux-arm-gnueabihf": "npm:1.9.1"
- "@unrs/resolver-binding-linux-arm-musleabihf": "npm:1.9.1"
- "@unrs/resolver-binding-linux-arm64-gnu": "npm:1.9.1"
- "@unrs/resolver-binding-linux-arm64-musl": "npm:1.9.1"
- "@unrs/resolver-binding-linux-ppc64-gnu": "npm:1.9.1"
- "@unrs/resolver-binding-linux-riscv64-gnu": "npm:1.9.1"
- "@unrs/resolver-binding-linux-riscv64-musl": "npm:1.9.1"
- "@unrs/resolver-binding-linux-s390x-gnu": "npm:1.9.1"
- "@unrs/resolver-binding-linux-x64-gnu": "npm:1.9.1"
- "@unrs/resolver-binding-linux-x64-musl": "npm:1.9.1"
- "@unrs/resolver-binding-wasm32-wasi": "npm:1.9.1"
- "@unrs/resolver-binding-win32-arm64-msvc": "npm:1.9.1"
- "@unrs/resolver-binding-win32-ia32-msvc": "npm:1.9.1"
- "@unrs/resolver-binding-win32-x64-msvc": "npm:1.9.1"
- napi-postinstall: "npm:^0.2.2"
+ "@unrs/resolver-binding-android-arm-eabi": "npm:1.9.2"
+ "@unrs/resolver-binding-android-arm64": "npm:1.9.2"
+ "@unrs/resolver-binding-darwin-arm64": "npm:1.9.2"
+ "@unrs/resolver-binding-darwin-x64": "npm:1.9.2"
+ "@unrs/resolver-binding-freebsd-x64": "npm:1.9.2"
+ "@unrs/resolver-binding-linux-arm-gnueabihf": "npm:1.9.2"
+ "@unrs/resolver-binding-linux-arm-musleabihf": "npm:1.9.2"
+ "@unrs/resolver-binding-linux-arm64-gnu": "npm:1.9.2"
+ "@unrs/resolver-binding-linux-arm64-musl": "npm:1.9.2"
+ "@unrs/resolver-binding-linux-ppc64-gnu": "npm:1.9.2"
+ "@unrs/resolver-binding-linux-riscv64-gnu": "npm:1.9.2"
+ "@unrs/resolver-binding-linux-riscv64-musl": "npm:1.9.2"
+ "@unrs/resolver-binding-linux-s390x-gnu": "npm:1.9.2"
+ "@unrs/resolver-binding-linux-x64-gnu": "npm:1.9.2"
+ "@unrs/resolver-binding-linux-x64-musl": "npm:1.9.2"
+ "@unrs/resolver-binding-wasm32-wasi": "npm:1.9.2"
+ "@unrs/resolver-binding-win32-arm64-msvc": "npm:1.9.2"
+ "@unrs/resolver-binding-win32-ia32-msvc": "npm:1.9.2"
+ "@unrs/resolver-binding-win32-x64-msvc": "npm:1.9.2"
+ napi-postinstall: "npm:^0.2.4"
dependenciesMeta:
"@unrs/resolver-binding-android-arm-eabi":
optional: true
@@ -9233,7 +9333,7 @@ __metadata:
optional: true
"@unrs/resolver-binding-win32-x64-msvc":
optional: true
- checksum: 10c0/fded9251b6c180c92c0510abe63e4fa9a5a4adcdcf3c9f7920507dc9f1ec756de5e71d1258f12bf4a32f7042e1fe142b6dc1003d8a6fb4d0bf1234226c879b01
+ checksum: 10c0/e3481cc19ea4b25f888e2412bbd80a729b13527a41b035e784b71d1a7d4e2109b58b174adce989085eb75c787435e80ffb385db2b1598288474f53beb01438c0
languageName: node
linkType: hard
@@ -9518,7 +9618,7 @@ __metadata:
languageName: node
linkType: hard
-"yaml@npm:^2.3.4, yaml@npm:^2.5.0, yaml@npm:^2.7.1":
+"yaml@npm:^2.3.4, yaml@npm:^2.5.0, yaml@npm:^2.7.1, yaml@npm:^2.8.0":
version: 2.8.0
resolution: "yaml@npm:2.8.0"
bin:
From 9225d8435a1917cd2a9db2705c5080bcf32c14ab Mon Sep 17 00:00:00 2001
From: Micha
Date: Sun, 22 Jun 2025 22:39:46 +0200
Subject: [PATCH 14/16] fix: add new Logos for equal hight in Sidebar
---
src/assets/logo/logo-export.ts | 8 ++++----
src/assets/logo/new/logo_colored_combo_dark.svg | 17 +++++++++++++++++
.../logo/new/logo_colored_combo_light.svg | 17 +++++++++++++++++
.../logo/new/logo_colored_submark_dark.svg | 10 ++++++++++
.../logo/new/logo_colored_submark_light.svg | 10 ++++++++++
src/components/custom-ui/app-sidebar.tsx | 6 +++---
6 files changed, 61 insertions(+), 7 deletions(-)
create mode 100644 src/assets/logo/new/logo_colored_combo_dark.svg
create mode 100644 src/assets/logo/new/logo_colored_combo_light.svg
create mode 100644 src/assets/logo/new/logo_colored_submark_dark.svg
create mode 100644 src/assets/logo/new/logo_colored_submark_light.svg
diff --git a/src/assets/logo/logo-export.ts b/src/assets/logo/logo-export.ts
index 44681d3..17a0708 100644
--- a/src/assets/logo/logo-export.ts
+++ b/src/assets/logo/logo-export.ts
@@ -1,5 +1,5 @@
-export { default as logo_colored_combo_light } from '@/assets/logo/logo_colored_combo_light.svg';
-export { default as logo_colored_combo_dark } from '@/assets/logo/logo_colored_combo_dark.svg';
+export { default as logo_colored_combo_light } from '@/assets/logo/new/logo_colored_combo_light.svg';
+export { default as logo_colored_combo_dark } from '@/assets/logo/new/logo_colored_combo_dark.svg';
export { default as logo_colored_primary_light } from '@/assets/logo/logo_colored_primary_light.svg';
export { default as logo_colored_primary_dark } from '@/assets/logo/logo_colored_primary_dark.svg';
export { default as logo_colored_secondary_light } from '@/assets/logo/logo_colored_secondary_light.svg';
@@ -12,5 +12,5 @@ export { default as logo_mono_secondary_light } from '@/assets/logo/logo_mono_se
export { default as logo_mono_secondary_dark } from '@/assets/logo/logo_mono_secondary_dark.svg';
export { default as logo_mono_submark_light } from '@/assets/logo/logo_mono_submark_light.svg';
export { default as logo_mono_submark_dark } from '@/assets/logo/logo_mono_submark_dark.svg';
-export { default as logo_colored_submark_light } from '@/assets/logo/logo_colored_submark_light.svg';
-export { default as logo_colored_submark_dark } from '@/assets/logo/logo_colored_submark_dark.svg';
+export { default as logo_colored_submark_light } from '@/assets/logo/new/logo_colored_submark_light.svg';
+export { default as logo_colored_submark_dark } from '@/assets/logo/new/logo_colored_submark_dark.svg';
diff --git a/src/assets/logo/new/logo_colored_combo_dark.svg b/src/assets/logo/new/logo_colored_combo_dark.svg
new file mode 100644
index 0000000..ec4acae
--- /dev/null
+++ b/src/assets/logo/new/logo_colored_combo_dark.svg
@@ -0,0 +1,17 @@
+
diff --git a/src/assets/logo/new/logo_colored_combo_light.svg b/src/assets/logo/new/logo_colored_combo_light.svg
new file mode 100644
index 0000000..33e6c6d
--- /dev/null
+++ b/src/assets/logo/new/logo_colored_combo_light.svg
@@ -0,0 +1,17 @@
+
diff --git a/src/assets/logo/new/logo_colored_submark_dark.svg b/src/assets/logo/new/logo_colored_submark_dark.svg
new file mode 100644
index 0000000..efadbf0
--- /dev/null
+++ b/src/assets/logo/new/logo_colored_submark_dark.svg
@@ -0,0 +1,10 @@
+
diff --git a/src/assets/logo/new/logo_colored_submark_light.svg b/src/assets/logo/new/logo_colored_submark_light.svg
new file mode 100644
index 0000000..cc7b3e1
--- /dev/null
+++ b/src/assets/logo/new/logo_colored_submark_light.svg
@@ -0,0 +1,10 @@
+
diff --git a/src/components/custom-ui/app-sidebar.tsx b/src/components/custom-ui/app-sidebar.tsx
index b279c73..ae00c9b 100644
--- a/src/components/custom-ui/app-sidebar.tsx
+++ b/src/components/custom-ui/app-sidebar.tsx
@@ -81,14 +81,14 @@ export function AppSidebar() {
From 1d9ab84047ab07715f5a3feac65cb6e3b6794756 Mon Sep 17 00:00:00 2001
From: Maximilian Liebmann
Date: Sun, 22 Jun 2025 23:22:53 +0200
Subject: [PATCH 15/16] feat: enhance header with notification buttons and user
dropdown
- Updated header component to include notification buttons with icons.
- Introduced a new NavUser component for user-related actions in the sidebar.
- Added NotificationDot component for visual notification indicators.
- Created UserCard component to display user information.
- Implemented UserDropdown for user settings and logout functionality.
- Added Avatar component for user images with fallback support.
- Refactored Sheet and Tooltip components for consistency and improved styling.
- Introduced QueryProvider for managing React Query context.
- Updated SidebarProvider to use custom sidebar implementation.
- Enhanced mobile detection hook for better responsiveness.
- Updated dependencies in yarn.lock for new features and fixes.
feat: remove dot
---
package.json | 3 +-
src/app/globals.css | 3 +
src/app/layout.tsx | 2 +-
.../buttons/notification-button.tsx | 40 +++++++
src/components/custom-ui/app-sidebar.tsx | 32 +++--
src/components/{ui => custom-ui}/sidebar.tsx | 2 +-
src/components/misc/header.tsx | 34 +++++-
src/components/misc/nav-user.tsx | 110 ++++++++++++++++++
src/components/misc/notification-dot.tsx | 35 ++++++
src/components/misc/user-card.tsx | 29 +++++
src/components/misc/user-dropdown.tsx | 59 ++++++++++
src/components/ui/avatar.tsx | 53 +++++++++
src/components/ui/sheet.tsx | 90 +++++++-------
src/components/ui/skeleton.tsx | 12 +-
src/components/ui/tooltip.tsx | 30 ++---
.../{ => wrappers}/query-provider.tsx | 0
src/components/wrappers/sidebar-provider.tsx | 2 +-
src/hooks/use-mobile.ts | 24 ++--
yarn.lock | 45 ++++++-
19 files changed, 501 insertions(+), 104 deletions(-)
create mode 100644 src/components/buttons/notification-button.tsx
rename src/components/{ui => custom-ui}/sidebar.tsx (99%)
create mode 100644 src/components/misc/nav-user.tsx
create mode 100644 src/components/misc/notification-dot.tsx
create mode 100644 src/components/misc/user-card.tsx
create mode 100644 src/components/misc/user-dropdown.tsx
create mode 100644 src/components/ui/avatar.tsx
rename src/components/{ => wrappers}/query-provider.tsx (100%)
diff --git a/package.json b/package.json
index 1623ef9..2ef0160 100644
--- a/package.json
+++ b/package.json
@@ -27,9 +27,10 @@
"@fortawesome/react-fontawesome": "^0.2.2",
"@hookform/resolvers": "^5.0.1",
"@prisma/client": "^6.9.0",
+ "@radix-ui/react-avatar": "^1.1.10",
"@radix-ui/react-collapsible": "^1.1.11",
"@radix-ui/react-dialog": "^1.1.14",
- "@radix-ui/react-dropdown-menu": "^2.1.14",
+ "@radix-ui/react-dropdown-menu": "^2.1.15",
"@radix-ui/react-hover-card": "^1.1.13",
"@radix-ui/react-label": "^2.1.6",
"@radix-ui/react-scroll-area": "^1.2.8",
diff --git a/src/app/globals.css b/src/app/globals.css
index a5f7eaf..93a24ce 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -33,6 +33,7 @@
--text-alt: var(--neutral-900);
--text-input: var(--text);
--text-muted-input: var(--neutral-450);
+ --text-muted: var(--neutral-300);
--muted-input: var(--neutral-600);
--background-disabled: var(--neutral-500);
--text-disabled: var(--neutral-700);
@@ -157,6 +158,7 @@
--color-text-alt: var(--text-alt);
--color-text-input: var(--text-input);
--color-text-muted-input: var(--text-muted-input);
+ --color-text-muted: var(--text-muted);
--color-muted-input: var(--muted-input);
--color-background-disabled: var(--neutral-500);
@@ -280,6 +282,7 @@
--text-alt: var(--neutral-900);
--text-input: var(--text);
--text-muted-input: var(--neutral-450);
+ --text-muted: var(--neutral-300);
--muted-input: var(--neutral-500);
--background-disabled: var(--neutral-500);
--text-disabled: var(--neutral-700);
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 201a730..af40867 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -2,7 +2,7 @@ import { ThemeProvider } from '@/components/wrappers/theme-provider';
import type { Metadata } from 'next';
import './globals.css';
-import { QueryProvider } from '@/components/query-provider';
+import { QueryProvider } from '@/components/wrappers/query-provider';
export const metadata: Metadata = {
title: 'MeetUp',
diff --git a/src/components/buttons/notification-button.tsx b/src/components/buttons/notification-button.tsx
new file mode 100644
index 0000000..0b718f9
--- /dev/null
+++ b/src/components/buttons/notification-button.tsx
@@ -0,0 +1,40 @@
+import { Button } from '@/components/ui/button';
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ // DropdownMenuGroup,
+ // DropdownMenuItem,
+ // DropdownMenuLabel,
+ // DropdownMenuPortal,
+ // DropdownMenuSeparator,
+ // DropdownMenuShortcut,
+ // DropdownMenuSub,
+ // DropdownMenuSubContent,
+ // DropdownMenuSubTrigger,
+ DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu';
+import { NDot, NotificationDot } from '@/components/misc/notification-dot';
+
+export function NotificationButton({
+ dotVariant,
+ children,
+ ...props
+}: {
+ dotVariant?: NDot;
+ children: React.ReactNode;
+} & React.ComponentProps) {
+ return (
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/custom-ui/app-sidebar.tsx b/src/components/custom-ui/app-sidebar.tsx
index ae00c9b..4861363 100644
--- a/src/components/custom-ui/app-sidebar.tsx
+++ b/src/components/custom-ui/app-sidebar.tsx
@@ -6,27 +6,27 @@ import {
SidebarContent,
SidebarFooter,
SidebarGroup,
- SidebarGroupAction,
+ // SidebarGroupAction,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
- SidebarInput,
- SidebarInset,
+ // SidebarInput,
+ // SidebarInset,
SidebarMenu,
- SidebarMenuAction,
- SidebarMenuBadge,
+ // SidebarMenuAction,
+ // SidebarMenuBadge,
SidebarMenuButton,
SidebarMenuItem,
- SidebarMenuSkeleton,
- SidebarMenuSub,
- SidebarMenuSubButton,
- SidebarMenuSubItem,
- SidebarProvider,
- SidebarRail,
- SidebarSeparator,
- SidebarTrigger,
- useSidebar,
-} from '@/components/ui/sidebar';
+ // SidebarMenuSkeleton,
+ // SidebarMenuSub,
+ // SidebarMenuSubButton,
+ // SidebarMenuSubItem,
+ // SidebarProvider,
+ // SidebarRail,
+ // SidebarSeparator,
+ // SidebarTrigger,
+ // useSidebar,
+} from '@/components/custom-ui/sidebar';
import { ChevronDown } from 'lucide-react';
import {
@@ -39,8 +39,6 @@ import Logo from '@/components/misc/logo';
import Link from 'next/link';
-import { ThemePicker } from '@/components/misc/theme-picker';
-
import {
Star,
CalendarDays,
diff --git a/src/components/ui/sidebar.tsx b/src/components/custom-ui/sidebar.tsx
similarity index 99%
rename from src/components/ui/sidebar.tsx
rename to src/components/custom-ui/sidebar.tsx
index 6b68b8f..11228cb 100644
--- a/src/components/ui/sidebar.tsx
+++ b/src/components/custom-ui/sidebar.tsx
@@ -466,7 +466,7 @@ function SidebarMenuItem({ className, ...props }: React.ComponentProps<'li'>) {
);
diff --git a/src/components/misc/header.tsx b/src/components/misc/header.tsx
index dbf8a1f..ed53953 100644
--- a/src/components/misc/header.tsx
+++ b/src/components/misc/header.tsx
@@ -1,5 +1,22 @@
-import { SidebarTrigger } from '@/components/ui/sidebar';
+import { SidebarTrigger } from '@/components/custom-ui/sidebar';
import { ThemePicker } from '@/components/misc/theme-picker';
+import { NotificationButton } from '@/components/buttons/notification-button';
+
+import { BellRing, Inbox } from 'lucide-react';
+import UserDropdown from '@/components/misc/user-dropdown';
+
+const items = [
+ {
+ title: 'Calendar',
+ url: '#',
+ icon: Inbox,
+ },
+ {
+ title: 'Friends',
+ url: '#',
+ icon: BellRing,
+ },
+];
export default function Header({
children,
@@ -8,13 +25,24 @@ export default function Header({
}>) {
return (
-
+
Search
-
+
+ {items.map((item) => (
+
+
+
+ ))}
+
{children}
diff --git a/src/components/misc/nav-user.tsx b/src/components/misc/nav-user.tsx
new file mode 100644
index 0000000..53ab582
--- /dev/null
+++ b/src/components/misc/nav-user.tsx
@@ -0,0 +1,110 @@
+'use client';
+
+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/custom-ui/sidebar';
+
+export function NavUser({
+ user,
+}: {
+ user: {
+ name: string;
+ email: string;
+ avatar: string;
+ };
+}) {
+ const { isMobile } = useSidebar();
+
+ return (
+
+
+
+
+
+
+
+ CN
+
+
+ {user.name}
+ {user.email}
+
+
+
+
+
+
+
+
+
+ CN
+
+
+ {user.name}
+ {user.email}
+
+
+
+
+
+
+
+ Upgrade to Pro
+
+
+
+
+
+
+ Account
+
+
+
+ Billing
+
+
+
+ Notifications
+
+
+
+
+
+ Log out
+
+
+
+
+
+ );
+}
diff --git a/src/components/misc/notification-dot.tsx b/src/components/misc/notification-dot.tsx
new file mode 100644
index 0000000..a918188
--- /dev/null
+++ b/src/components/misc/notification-dot.tsx
@@ -0,0 +1,35 @@
+import { cn } from '@/lib/utils';
+import { cva, type VariantProps } from 'class-variance-authority';
+import { CircleSmall } from 'lucide-react';
+
+const dotVariants = cva('', {
+ variants: {
+ variant: {
+ neutral: 'fill-neutral-900',
+ active: 'fill-red-600 stroke-red-600',
+ hidden: 'hidden',
+ },
+ },
+ defaultVariants: {
+ variant: 'hidden',
+ },
+});
+
+function NotificationDot({
+ className,
+ dotVariant,
+ ...props
+}: {
+ className: string;
+ dotVariant: VariantProps['variant'];
+}) {
+ return (
+
+ );
+}
+
+export type NDot = VariantProps['variant'];
+export { NotificationDot, dotVariants };
diff --git a/src/components/misc/user-card.tsx b/src/components/misc/user-card.tsx
new file mode 100644
index 0000000..faefc35
--- /dev/null
+++ b/src/components/misc/user-card.tsx
@@ -0,0 +1,29 @@
+import { useGetApiUserMe } from '@/generated/api/user/user';
+import { Avatar } from '@/components/ui/avatar';
+import Image from 'next/image';
+import { User } from 'lucide-react';
+
+export default function UserCard() {
+ const { data } = useGetApiUserMe();
+ return (
+
+
+ {data?.data.user.image ? (
+
+ ) : (
+
+ )}
+
+
{data?.data.user.name}
+
+ {data?.data.user.email}
+
+
+ );
+}
diff --git a/src/components/misc/user-dropdown.tsx b/src/components/misc/user-dropdown.tsx
new file mode 100644
index 0000000..8f5aa05
--- /dev/null
+++ b/src/components/misc/user-dropdown.tsx
@@ -0,0 +1,59 @@
+'use client';
+
+import { Avatar } from '@/components/ui/avatar';
+import { Button } from '@/components/ui/button';
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuItem,
+ // DropdownMenuLabel,
+ // DropdownMenuPortal,
+ DropdownMenuSeparator,
+ // DropdownMenuShortcut,
+ // DropdownMenuSub,
+ // DropdownMenuSubContent,
+ // DropdownMenuSubTrigger,
+ DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu';
+import { useGetApiUserMe } from '@/generated/api/user/user';
+import { ChevronDown, User } from 'lucide-react';
+import Image from 'next/image';
+import Link from 'next/link';
+import UserCard from '@/components/misc/user-card';
+
+export default function UserDropdown() {
+ const { data } = useGetApiUserMe();
+ return (
+
+
+
+
+
+
+
+
+
+ Settings
+
+
+ Logout
+
+
+
+ );
+}
diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx
new file mode 100644
index 0000000..6a21b65
--- /dev/null
+++ b/src/components/ui/avatar.tsx
@@ -0,0 +1,53 @@
+'use client';
+
+import * as React from 'react';
+import * as AvatarPrimitive from '@radix-ui/react-avatar';
+
+import { cn } from '@/lib/utils';
+
+function Avatar({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AvatarImage({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AvatarFallback({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export { Avatar, AvatarImage, AvatarFallback };
diff --git a/src/components/ui/sheet.tsx b/src/components/ui/sheet.tsx
index 84649ad..e8d5ec1 100644
--- a/src/components/ui/sheet.tsx
+++ b/src/components/ui/sheet.tsx
@@ -1,31 +1,31 @@
-"use client"
+'use client';
-import * as React from "react"
-import * as SheetPrimitive from "@radix-ui/react-dialog"
-import { XIcon } from "lucide-react"
+import * as React from 'react';
+import * as SheetPrimitive from '@radix-ui/react-dialog';
+import { XIcon } from 'lucide-react';
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils';
function Sheet({ ...props }: React.ComponentProps) {
- return
+ return ;
}
function SheetTrigger({
...props
}: React.ComponentProps) {
- return
+ return ;
}
function SheetClose({
...props
}: React.ComponentProps) {
- return
+ return ;
}
function SheetPortal({
...props
}: React.ComponentProps) {
- return
+ return ;
}
function SheetOverlay({
@@ -34,71 +34,71 @@ function SheetOverlay({
}: React.ComponentProps) {
return (
- )
+ );
}
function SheetContent({
className,
children,
- side = "right",
+ side = 'right',
...props
}: React.ComponentProps & {
- side?: "top" | "right" | "bottom" | "left"
+ side?: 'top' | 'right' | 'bottom' | 'left';
}) {
return (
{children}
-
-
- Close
+
+
+ Close
- )
+ );
}
-function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
+function SheetHeader({ className, ...props }: React.ComponentProps<'div'>) {
return (
- )
+ );
}
-function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
+function SheetFooter({ className, ...props }: React.ComponentProps<'div'>) {
return (
- )
+ );
}
function SheetTitle({
@@ -107,11 +107,11 @@ function SheetTitle({
}: React.ComponentProps) {
return (
- )
+ );
}
function SheetDescription({
@@ -120,11 +120,11 @@ function SheetDescription({
}: React.ComponentProps) {
return (
- )
+ );
}
export {
@@ -136,4 +136,4 @@ export {
SheetFooter,
SheetTitle,
SheetDescription,
-}
+};
diff --git a/src/components/ui/skeleton.tsx b/src/components/ui/skeleton.tsx
index 32ea0ef..a9344b2 100644
--- a/src/components/ui/skeleton.tsx
+++ b/src/components/ui/skeleton.tsx
@@ -1,13 +1,13 @@
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils';
-function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
+function Skeleton({ className, ...props }: React.ComponentProps<'div'>) {
return (
- )
+ );
}
-export { Skeleton }
+export { Skeleton };
diff --git a/src/components/ui/tooltip.tsx b/src/components/ui/tooltip.tsx
index 4ee26b3..2b8b1d7 100644
--- a/src/components/ui/tooltip.tsx
+++ b/src/components/ui/tooltip.tsx
@@ -1,9 +1,9 @@
-"use client"
+'use client';
-import * as React from "react"
-import * as TooltipPrimitive from "@radix-ui/react-tooltip"
+import * as React from 'react';
+import * as TooltipPrimitive from '@radix-ui/react-tooltip';
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils';
function TooltipProvider({
delayDuration = 0,
@@ -11,11 +11,11 @@ function TooltipProvider({
}: React.ComponentProps) {
return (
- )
+ );
}
function Tooltip({
@@ -23,15 +23,15 @@ function Tooltip({
}: React.ComponentProps) {
return (
-
+
- )
+ );
}
function TooltipTrigger({
...props
}: React.ComponentProps) {
- return
+ return ;
}
function TooltipContent({
@@ -43,19 +43,19 @@ function TooltipContent({
return (
{children}
-
+
- )
+ );
}
-export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
+export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
diff --git a/src/components/query-provider.tsx b/src/components/wrappers/query-provider.tsx
similarity index 100%
rename from src/components/query-provider.tsx
rename to src/components/wrappers/query-provider.tsx
diff --git a/src/components/wrappers/sidebar-provider.tsx b/src/components/wrappers/sidebar-provider.tsx
index 3873fa4..3c9ff95 100644
--- a/src/components/wrappers/sidebar-provider.tsx
+++ b/src/components/wrappers/sidebar-provider.tsx
@@ -1,7 +1,7 @@
'use client';
import React from 'react';
-import { SidebarProvider } from '../ui/sidebar';
+import { SidebarProvider } from '../custom-ui/sidebar';
export default function SidebarProviderWrapper({
defaultOpen,
diff --git a/src/hooks/use-mobile.ts b/src/hooks/use-mobile.ts
index 2b0fe1d..821f8ff 100644
--- a/src/hooks/use-mobile.ts
+++ b/src/hooks/use-mobile.ts
@@ -1,19 +1,21 @@
-import * as React from "react"
+import * as React from 'react';
-const MOBILE_BREAKPOINT = 768
+const MOBILE_BREAKPOINT = 768;
export function useIsMobile() {
- const [isMobile, setIsMobile] = React.useState(undefined)
+ const [isMobile, setIsMobile] = React.useState(
+ undefined,
+ );
React.useEffect(() => {
- const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
const onChange = () => {
- setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
- }
- mql.addEventListener("change", onChange)
- setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
- return () => mql.removeEventListener("change", onChange)
- }, [])
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
+ };
+ mql.addEventListener('change', onChange);
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
+ return () => mql.removeEventListener('change', onChange);
+ }, []);
- return !!isMobile
+ return !!isMobile;
}
diff --git a/yarn.lock b/yarn.lock
index 3328163..ba978f0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1299,6 +1299,29 @@ __metadata:
languageName: node
linkType: hard
+"@radix-ui/react-avatar@npm:^1.1.10":
+ version: 1.1.10
+ resolution: "@radix-ui/react-avatar@npm:1.1.10"
+ dependencies:
+ "@radix-ui/react-context": "npm:1.1.2"
+ "@radix-ui/react-primitive": "npm:2.1.3"
+ "@radix-ui/react-use-callback-ref": "npm:1.1.1"
+ "@radix-ui/react-use-is-hydrated": "npm:0.1.0"
+ "@radix-ui/react-use-layout-effect": "npm:1.1.1"
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+ checksum: 10c0/9fb0cf9a9d0fdbeaa2efda476402fc09db2e6ff9cd9aa3ea1d315d9c9579840722a4833725cb196c455e0bd775dfe04221a4f6855685ce89d2133c42e2b07e5f
+ languageName: node
+ linkType: hard
+
"@radix-ui/react-collapsible@npm:^1.1.11":
version: 1.1.11
resolution: "@radix-ui/react-collapsible@npm:1.1.11"
@@ -1441,7 +1464,7 @@ __metadata:
languageName: node
linkType: hard
-"@radix-ui/react-dropdown-menu@npm:^2.1.14":
+"@radix-ui/react-dropdown-menu@npm:^2.1.15":
version: 2.1.15
resolution: "@radix-ui/react-dropdown-menu@npm:2.1.15"
dependencies:
@@ -1951,6 +1974,21 @@ __metadata:
languageName: node
linkType: hard
+"@radix-ui/react-use-is-hydrated@npm:0.1.0":
+ version: 0.1.0
+ resolution: "@radix-ui/react-use-is-hydrated@npm:0.1.0"
+ dependencies:
+ use-sync-external-store: "npm:^1.5.0"
+ peerDependencies:
+ "@types/react": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ checksum: 10c0/635079bafe32829fc7405895154568ea94a22689b170489fd6d77668e4885e72ff71ed6d0ea3d602852841ef0f1927aa400fee2178d5dfbeb8bc9297da7d6498
+ languageName: node
+ linkType: hard
+
"@radix-ui/react-use-layout-effect@npm:1.1.1":
version: 1.1.1
resolution: "@radix-ui/react-use-layout-effect@npm:1.1.1"
@@ -6637,9 +6675,10 @@ __metadata:
"@fortawesome/react-fontawesome": "npm:^0.2.2"
"@hookform/resolvers": "npm:^5.0.1"
"@prisma/client": "npm:^6.9.0"
+ "@radix-ui/react-avatar": "npm:^1.1.10"
"@radix-ui/react-collapsible": "npm:^1.1.11"
"@radix-ui/react-dialog": "npm:^1.1.14"
- "@radix-ui/react-dropdown-menu": "npm:^2.1.14"
+ "@radix-ui/react-dropdown-menu": "npm:^2.1.15"
"@radix-ui/react-hover-card": "npm:^1.1.13"
"@radix-ui/react-label": "npm:^2.1.6"
"@radix-ui/react-scroll-area": "npm:^1.2.8"
@@ -9394,7 +9433,7 @@ __metadata:
languageName: node
linkType: hard
-"use-sync-external-store@npm:^1.4.0":
+"use-sync-external-store@npm:^1.4.0, use-sync-external-store@npm:^1.5.0":
version: 1.5.0
resolution: "use-sync-external-store@npm:1.5.0"
peerDependencies:
From 8bee6ede3fc144c0c3013e7c3c448565f2befaef Mon Sep 17 00:00:00 2001
From: SomeCodecat <88855796+SomeCodecat@users.noreply.github.com>
Date: Wed, 25 Jun 2025 12:01:04 +0200
Subject: [PATCH 16/16] refactor: remove unused imports from notification
button and user dropdown components
---
src/components/buttons/notification-button.tsx | 9 ---------
src/components/custom-ui/app-sidebar.tsx | 14 --------------
src/components/misc/user-dropdown.tsx | 7 -------
3 files changed, 30 deletions(-)
diff --git a/src/components/buttons/notification-button.tsx b/src/components/buttons/notification-button.tsx
index 0b718f9..f41f325 100644
--- a/src/components/buttons/notification-button.tsx
+++ b/src/components/buttons/notification-button.tsx
@@ -2,15 +2,6 @@ import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
- // DropdownMenuGroup,
- // DropdownMenuItem,
- // DropdownMenuLabel,
- // DropdownMenuPortal,
- // DropdownMenuSeparator,
- // DropdownMenuShortcut,
- // DropdownMenuSub,
- // DropdownMenuSubContent,
- // DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { NDot, NotificationDot } from '@/components/misc/notification-dot';
diff --git a/src/components/custom-ui/app-sidebar.tsx b/src/components/custom-ui/app-sidebar.tsx
index 4861363..f823970 100644
--- a/src/components/custom-ui/app-sidebar.tsx
+++ b/src/components/custom-ui/app-sidebar.tsx
@@ -6,26 +6,12 @@ import {
SidebarContent,
SidebarFooter,
SidebarGroup,
- // SidebarGroupAction,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
- // SidebarInput,
- // SidebarInset,
SidebarMenu,
- // SidebarMenuAction,
- // SidebarMenuBadge,
SidebarMenuButton,
SidebarMenuItem,
- // SidebarMenuSkeleton,
- // SidebarMenuSub,
- // SidebarMenuSubButton,
- // SidebarMenuSubItem,
- // SidebarProvider,
- // SidebarRail,
- // SidebarSeparator,
- // SidebarTrigger,
- // useSidebar,
} from '@/components/custom-ui/sidebar';
import { ChevronDown } from 'lucide-react';
diff --git a/src/components/misc/user-dropdown.tsx b/src/components/misc/user-dropdown.tsx
index 8f5aa05..e55f4bb 100644
--- a/src/components/misc/user-dropdown.tsx
+++ b/src/components/misc/user-dropdown.tsx
@@ -5,15 +5,8 @@ import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
- DropdownMenuGroup,
DropdownMenuItem,
- // DropdownMenuLabel,
- // DropdownMenuPortal,
DropdownMenuSeparator,
- // DropdownMenuShortcut,
- // DropdownMenuSub,
- // DropdownMenuSubContent,
- // DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { useGetApiUserMe } from '@/generated/api/user/user';