From 7ce8ae0867e464509c7fdace1723937a6546b18e Mon Sep 17 00:00:00 2001 From: shadcn Date: Fri, 16 Jun 2023 20:38:13 +0400 Subject: [PATCH 1/4] fix: dropdown menu --- app/layout.tsx | 4 +- components/header.tsx | 16 ++-- components/login-button.tsx | 20 ++--- components/sidebar-list.tsx | 2 - components/ui/dropdown-menu.tsx | 128 ++++++++++++++++++++++++++++++++ components/ui/icons.tsx | 20 ++++- components/ui/user-menu.tsx | 94 ----------------------- components/user-menu.tsx | 65 ++++++++++++++++ 8 files changed, 230 insertions(+), 119 deletions(-) create mode 100644 components/ui/dropdown-menu.tsx delete mode 100644 components/ui/user-menu.tsx create mode 100644 components/user-menu.tsx diff --git a/app/layout.tsx b/app/layout.tsx index 4305299..0b46bf8 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -43,10 +43,10 @@ export default function RootLayout({ children }: RootLayoutProps) { > -
+
{/* @ts-ignore */}
-
{children}
+
{children}
diff --git a/components/header.tsx b/components/header.tsx index a6e9acd..a8ff321 100644 --- a/components/header.tsx +++ b/components/header.tsx @@ -12,13 +12,13 @@ import { ClearHistory } from '@/components/clear-history' import { clearChats } from '@/app/actions' import Link from 'next/link' import { auth } from '@/auth' -import { UserMenu } from './ui/user-menu' +import { UserMenu } from './user-menu' import { LoginButton } from './login-button' export async function Header() { const session = await auth() return ( -
+
{session?.user ? ( @@ -33,12 +33,16 @@ export async function Header() { ) : ( - + )}
- - {session?.user ? : } + + {session?.user ? ( + + ) : ( + + )}
@@ -49,7 +53,7 @@ export async function Header() { className={cn(buttonVariants({ variant: 'outline' }))} > - GitHub + GitHub { setIsLoading(true) signIn('github') @@ -29,12 +25,8 @@ export function LoginButton({ className={cn(className)} {...props} > - {isLoading ? ( - - ) : ( - - )} - {text} + {isLoading && } + Login ) } diff --git a/components/sidebar-list.tsx b/components/sidebar-list.tsx index 4eb3f66..301e705 100644 --- a/components/sidebar-list.tsx +++ b/components/sidebar-list.tsx @@ -10,8 +10,6 @@ export interface SidebarListProps { export async function SidebarList({ userId }: SidebarListProps) { const chats = await getChats(userId) - console.log(chats) - return (
{chats?.length ? ( diff --git a/components/ui/dropdown-menu.tsx b/components/ui/dropdown-menu.tsx new file mode 100644 index 0000000..184d4e6 --- /dev/null +++ b/components/ui/dropdown-menu.tsx @@ -0,0 +1,128 @@ +'use client' + +import * as React from 'react' +import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu' + +import { cn } from '@/lib/utils' + +const DropdownMenu = DropdownMenuPrimitive.Root + +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger + +const DropdownMenuGroup = DropdownMenuPrimitive.Group + +const DropdownMenuPortal = DropdownMenuPrimitive.Portal + +const DropdownMenuSub = DropdownMenuPrimitive.Sub + +const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup + +const DropdownMenuSubContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSubContent.displayName = + DropdownMenuPrimitive.SubContent.displayName + +const DropdownMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)) +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName + +const DropdownMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + +)) +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName + +const DropdownMenuLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + +)) +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName + +const DropdownMenuSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName + +const DropdownMenuShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ) +} +DropdownMenuShortcut.displayName = 'DropdownMenuShortcut' + +export { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuGroup, + DropdownMenuPortal, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuRadioGroup +} diff --git a/components/ui/icons.tsx b/components/ui/icons.tsx index ce59585..52b941a 100644 --- a/components/ui/icons.tsx +++ b/components/ui/icons.tsx @@ -442,6 +442,23 @@ function IconUsers({ className, ...props }: React.ComponentProps<'svg'>) { ) } +function IconExternalLink({ + className, + ...props +}: React.ComponentProps<'svg'>) { + return ( + + + + ) +} + export { IconEdit, IconNextChat, @@ -467,5 +484,6 @@ export { IconDownload, IconClose, IconShare, - IconUsers + IconUsers, + IconExternalLink } diff --git a/components/ui/user-menu.tsx b/components/ui/user-menu.tsx deleted file mode 100644 index a556b2c..0000000 --- a/components/ui/user-menu.tsx +++ /dev/null @@ -1,94 +0,0 @@ -'use client' - -// import { type Session } from 'next-auth' -import * as DropdownMenu from '@radix-ui/react-dropdown-menu' -import { signOut } from 'next-auth/react' - -import Image from 'next/image' - -export interface UserMenuProps { - session: any // Session -} - -export function UserMenu({ session }: UserMenuProps) { - return ( - - ) -} - -UserMenu.displayName = 'UserMenu' diff --git a/components/user-menu.tsx b/components/user-menu.tsx new file mode 100644 index 0000000..c74378e --- /dev/null +++ b/components/user-menu.tsx @@ -0,0 +1,65 @@ +'use client' + +import Image from 'next/image' +import { type Session } from 'next-auth' +import { signOut } from 'next-auth/react' + +import { Button } from '@/components/ui/button' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger +} from '@/components/ui/dropdown-menu' +import { IconExternalLink } from '@/components/ui/icons' + +export interface UserMenuProps { + user: Session['user'] +} + +export function UserMenu({ user }: UserMenuProps) { + return ( +
+ + + + + + +
{user?.name}
+
{user?.email}
+
+ + + + Vercel Homepage + + + + signOut()} className="text-xs"> + Log Out + +
+
+
+ ) +} From 071778533ca6931d895954b1a43d7a43db3e2b8b Mon Sep 17 00:00:00 2001 From: shadcn Date: Fri, 16 Jun 2023 21:28:44 +0400 Subject: [PATCH 2/4] fix: sign-in page --- app/sign-in/page.tsx | 8 ++------ components/header.tsx | 7 ++++++- components/login-button.tsx | 20 +++++++++++++++----- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/app/sign-in/page.tsx b/app/sign-in/page.tsx index bb63e65..00891e7 100644 --- a/app/sign-in/page.tsx +++ b/app/sign-in/page.tsx @@ -1,13 +1,9 @@ -import { FooterText } from '@/components/footer' import { LoginButton } from '@/components/login-button' export default function SignInPage() { return ( -
-
- - -
+
+
) } diff --git a/components/header.tsx b/components/header.tsx index a8ff321..469fc60 100644 --- a/components/header.tsx +++ b/components/header.tsx @@ -41,7 +41,12 @@ export async function Header() { {session?.user ? ( ) : ( - + )}
diff --git a/components/login-button.tsx b/components/login-button.tsx index 3fb7be0..54207f7 100644 --- a/components/login-button.tsx +++ b/components/login-button.tsx @@ -5,18 +5,24 @@ import { signIn } from 'next-auth/react' import { cn } from '@/lib/utils' import { Button, type ButtonProps } from '@/components/ui/button' -import { IconSpinner } from '@/components/ui/icons' +import { IconGitHub, IconSpinner } from '@/components/ui/icons' interface LoginButtonProps extends ButtonProps { + showGithubIcon?: boolean text?: string } -export function LoginButton({ className, ...props }: LoginButtonProps) { +export function LoginButton({ + text = 'Login with GitHub', + showGithubIcon = true, + className, + ...props +}: LoginButtonProps) { const [isLoading, setIsLoading] = React.useState(false) return ( ) } From cb941daca941148ad54642cf655a58972f6f885c Mon Sep 17 00:00:00 2001 From: shadcn Date: Fri, 16 Jun 2023 21:52:01 +0400 Subject: [PATCH 3/4] fix: actions --- app/actions.ts | 57 +++++-------- app/api/chat/route.ts | 3 +- app/chat/[id]/page.tsx | 27 ++++-- components/chat-message-actions.tsx | 3 +- components/chat-panel.tsx | 1 - components/chat.tsx | 3 +- components/clear-history.tsx | 12 ++- components/footer.tsx | 2 +- components/header.tsx | 17 ++-- components/providers.tsx | 1 + components/sidebar-actions.tsx | 14 +++- components/sidebar-list.tsx | 1 - components/ui/icons.tsx | 20 ++++- components/ui/select.tsx | 123 ++++++++++++++++++++++++++++ lib/types.ts | 7 +- package.json | 1 + pnpm-lock.yaml | 50 +++++++++++ 17 files changed, 277 insertions(+), 65 deletions(-) create mode 100644 components/ui/select.tsx diff --git a/app/actions.ts b/app/actions.ts index 451da5d..90c6973 100644 --- a/app/actions.ts +++ b/app/actions.ts @@ -1,9 +1,10 @@ 'use server' import { revalidatePath } from 'next/cache' +import { redirect } from 'next/navigation' import { kv } from '@vercel/kv' -import { auth } from '@/auth' +import { auth } from '@/auth' import { type Chat } from '@/lib/types' export async function getChats(userId?: string | null) { @@ -32,12 +33,8 @@ export async function getChats(userId?: string | null) { export async function getChat(id: string, userId: string) { const chat = await kv.hgetall(`chat:${id}`) - if (!chat) { - throw new Error('Not found') - } - - if (userId && chat.userId !== userId) { - throw new Error('Unauthorized') + if (!chat || (userId && chat.userId !== userId)) { + return null } return chat @@ -47,41 +44,36 @@ export async function removeChat({ id, path }: { id: string; path: string }) { const session = await auth() if (!session) { - throw new Error('Unauthorized') + return { + error: 'Unauthorized' + } } const uid = await kv.hget(`chat:${id}`, 'userId') if (uid !== session?.user?.id) { - throw new Error('Unauthorized') + return { + error: 'Unauthorized' + } } await kv.del(`chat:${id}`) await kv.zrem(`user:chat:${session.user.id}`, `chat:${id}`) revalidatePath('/') - revalidatePath(path) + return revalidatePath(path) } export async function clearChats() { const session = await auth() - if (!session) { - throw new Error('Unauthorized') - } - - if (!session.user?.id) { - throw new Error('Unauthorized') - } - - const chats: string[] = await kv.zrange( - `user:chat:${session.user.id}`, - 0, - -1, - { - rev: true + if (!session?.user?.id) { + return { + error: 'Unauthorized' } - ) + } + + const chats: string[] = await kv.zrange(`user:chat:${session.user.id}`, 0, -1) const pipeline = kv.pipeline() @@ -93,6 +85,7 @@ export async function clearChats() { await pipeline.exec() revalidatePath('/') + return redirect('/') } export async function getSharedChat(id: string) { @@ -108,16 +101,10 @@ export async function getSharedChat(id: string) { export async function shareChat(chat: Chat) { const session = await auth() - if (!session) { - throw new Error('Unauthorized') - } - - if (!session.user?.id) { - throw new Error('Unauthorized') - } - - if (chat.userId !== session.user?.id) { - throw new Error('Unauthorized') + if (!session?.user?.id || session.user.id !== chat.userId) { + return { + error: 'Unauthorized' + } } const payload = { diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts index 58008c3..beec80f 100644 --- a/app/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -1,8 +1,9 @@ import { kv } from '@vercel/kv' import { OpenAIStream, StreamingTextResponse } from 'ai-connector' import { Configuration, OpenAIApi } from 'openai-edge' -import { nanoid } from '@/lib/utils' + import { auth } from '@/auth' +import { nanoid } from '@/lib/utils' export const runtime = 'edge' diff --git a/app/chat/[id]/page.tsx b/app/chat/[id]/page.tsx index a3504bc..c1a569f 100644 --- a/app/chat/[id]/page.tsx +++ b/app/chat/[id]/page.tsx @@ -1,8 +1,9 @@ import { type Metadata } from 'next' -import { auth } from '@/auth' +import { notFound, redirect } from 'next/navigation' -import { Chat } from '@/components/chat' +import { auth } from '@/auth' import { getChat } from '@/app/actions' +import { Chat } from '@/components/chat' // export const runtime = 'edge' export const preferredRegion = 'home' @@ -16,16 +17,30 @@ export interface ChatPageProps { export async function generateMetadata({ params }: ChatPageProps): Promise { - const { user } = await auth() - const chat = await getChat(params.id, user?.id ?? '') + const session = await auth() + + if (!session?.user) { + return {} + } + + const chat = await getChat(params.id, session.user.id) return { title: chat?.title.slice(0, 50) ?? 'Chat' } } export default async function ChatPage({ params }: ChatPageProps) { - const { user } = await auth() - const chat = await getChat(params.id, user?.id ?? '') + const session = await auth() + + if (!session?.user) { + redirect('/sign-in') + } + + const chat = await getChat(params.id, session.user.id) + + if (!chat) { + notFound() + } return } diff --git a/components/chat-message-actions.tsx b/components/chat-message-actions.tsx index 732206a..b42c22f 100644 --- a/components/chat-message-actions.tsx +++ b/components/chat-message-actions.tsx @@ -1,10 +1,11 @@ 'use client' +import { type Message } from 'ai-connector' + import { Button } from '@/components/ui/button' import { IconCheck, IconCopy } from '@/components/ui/icons' import { useCopyToClipboard } from '@/lib/hooks/use-copy-to-clipboard' import { cn } from '@/lib/utils' -import { type Message } from 'ai-connector' interface ChatMessageActionsProps extends React.ComponentProps<'div'> { message: Message diff --git a/components/chat-panel.tsx b/components/chat-panel.tsx index b48e346..85ff951 100644 --- a/components/chat-panel.tsx +++ b/components/chat-panel.tsx @@ -1,7 +1,6 @@ import { type UseChatHelpers } from 'ai-connector/react' import { Button } from '@/components/ui/button' -import { ExternalLink } from '@/components/external-link' import { PromptForm } from '@/components/prompt-form' import { ButtonScrollToBottom } from '@/components/button-scroll-to-bottom' import { IconRefresh, IconStop } from '@/components/ui/icons' diff --git a/components/chat.tsx b/components/chat.tsx index 46049ea..165a77b 100644 --- a/components/chat.tsx +++ b/components/chat.tsx @@ -10,11 +10,10 @@ import { ChatScrollAnchor } from '@/components/chat-scroll-anchor' export interface ChatProps extends React.ComponentProps<'div'> { initialMessages?: Message[] - id?: string } -export function Chat({ id, title, initialMessages, className }: ChatProps) { +export function Chat({ id, initialMessages, className }: ChatProps) { const { messages, append, reload, stop, isLoading, input, setInput } = useChat({ initialMessages, diff --git a/components/clear-history.tsx b/components/clear-history.tsx index 1d1f7ed..16e3643 100644 --- a/components/clear-history.tsx +++ b/components/clear-history.tsx @@ -2,7 +2,9 @@ import * as React from 'react' import { useRouter } from 'next/navigation' +import { toast } from 'react-hot-toast' +import { ServerActionResult } from '@/lib/types' import { Button } from '@/components/ui/button' import { AlertDialog, @@ -18,7 +20,7 @@ import { import { IconSpinner } from '@/components/ui/icons' interface ClearHistoryProps { - clearChats: () => Promise + clearChats: () => ServerActionResult } export function ClearHistory({ clearChats }: ClearHistoryProps) { @@ -49,7 +51,13 @@ export function ClearHistory({ clearChats }: ClearHistoryProps) { onClick={event => { event.preventDefault() startTransition(async () => { - await clearChats() + const result = await clearChats() + + if (result && 'error' in result) { + toast.error(result.error) + return + } + setOpen(false) router.push('/') }) diff --git a/components/footer.tsx b/components/footer.tsx index 4b65095..0a8fce3 100644 --- a/components/footer.tsx +++ b/components/footer.tsx @@ -1,7 +1,7 @@ import React from 'react' -import { ExternalLink } from '@/components/external-link' import { cn } from '@/lib/utils' +import { ExternalLink } from '@/components/external-link' export function FooterText({ className, ...props }: React.ComponentProps<'p'>) { return ( diff --git a/components/header.tsx b/components/header.tsx index 469fc60..0353809 100644 --- a/components/header.tsx +++ b/components/header.tsx @@ -1,19 +1,18 @@ -import { Suspense, use } from 'react' +import * as React from 'react' +import Link from 'next/link' import { cn } from '@/lib/utils' +import { auth } from '@/auth' +import { clearChats } from '@/app/actions' import { buttonVariants } from '@/components/ui/button' import { Sidebar } from '@/components/sidebar' import { SidebarList } from '@/components/sidebar-list' import { IconGitHub, IconSeparator, IconVercel } from '@/components/ui/icons' - import { SidebarFooter } from '@/components/sidebar-footer' import { ThemeToggle } from '@/components/theme-toggle' import { ClearHistory } from '@/components/clear-history' -import { clearChats } from '@/app/actions' -import Link from 'next/link' -import { auth } from '@/auth' -import { UserMenu } from './user-menu' -import { LoginButton } from './login-button' +import { UserMenu } from '@/components/user-menu' +import { LoginButton } from '@/components/login-button' export async function Header() { const session = await auth() @@ -22,10 +21,10 @@ export async function Header() {
{session?.user ? ( - }> + }> {/* @ts-ignore */} - + diff --git a/components/providers.tsx b/components/providers.tsx index b4b9652..8922264 100644 --- a/components/providers.tsx +++ b/components/providers.tsx @@ -3,6 +3,7 @@ import * as React from 'react' import { ThemeProvider as NextThemesProvider } from 'next-themes' import { ThemeProviderProps } from 'next-themes/dist/types' + import { TooltipProvider } from '@/components/ui/tooltip' export function Providers({ children, ...props }: ThemeProviderProps) { diff --git a/components/sidebar-actions.tsx b/components/sidebar-actions.tsx index f50795a..4c6a34b 100644 --- a/components/sidebar-actions.tsx +++ b/components/sidebar-actions.tsx @@ -41,7 +41,7 @@ import { interface SidebarActionsProps { chat: Chat - removeChat: (args: { id: string; path: string }) => Promise + removeChat: (args: { id: string; path: string }) => ServerActionResult shareChat: (chat: Chat) => ServerActionResult } @@ -150,8 +150,8 @@ export function SidebarActions({ const result = await shareChat(chat) - if (!('id' in result)) { - toast.error(result.message) + if (result && 'error' in result) { + toast.error(result.error) return } @@ -189,10 +189,16 @@ export function SidebarActions({ onClick={event => { event.preventDefault() startRemoveTransition(async () => { - await removeChat({ + const result = await removeChat({ id: chat.id, path: chat.path }) + + if (result && 'error' in result) { + toast.error(result.error) + return + } + setDeleteDialogOpen(false) router.push('/') }) diff --git a/components/sidebar-list.tsx b/components/sidebar-list.tsx index 301e705..de2049a 100644 --- a/components/sidebar-list.tsx +++ b/components/sidebar-list.tsx @@ -1,6 +1,5 @@ import { getChats, removeChat, shareChat } from '@/app/actions' import { SidebarActions } from '@/components/sidebar-actions' - import { SidebarItem } from '@/components/sidebar-item' export interface SidebarListProps { diff --git a/components/ui/icons.tsx b/components/ui/icons.tsx index 52b941a..41ab4ea 100644 --- a/components/ui/icons.tsx +++ b/components/ui/icons.tsx @@ -459,6 +459,23 @@ function IconExternalLink({ ) } +function IconChevronUpDown({ + className, + ...props +}: React.ComponentProps<'svg'>) { + return ( + + + + ) +} + export { IconEdit, IconNextChat, @@ -485,5 +502,6 @@ export { IconClose, IconShare, IconUsers, - IconExternalLink + IconExternalLink, + IconChevronUpDown } diff --git a/components/ui/select.tsx b/components/ui/select.tsx new file mode 100644 index 0000000..77f12c2 --- /dev/null +++ b/components/ui/select.tsx @@ -0,0 +1,123 @@ +'use client' + +import * as React from 'react' +import * as SelectPrimitive from '@radix-ui/react-select' + +import { cn } from '@/lib/utils' +import { + IconArrowDown, + IconCheck, + IconChevronUpDown +} from '@/components/ui/icons' + +const Select = SelectPrimitive.Root + +const SelectGroup = SelectPrimitive.Group + +const SelectValue = SelectPrimitive.Value + +const SelectTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + {children} + + + + +)) +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName + +const SelectContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, position = 'popper', ...props }, ref) => ( + + + + {children} + + + +)) +SelectContent.displayName = SelectPrimitive.Content.displayName + +const SelectLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SelectLabel.displayName = SelectPrimitive.Label.displayName + +const SelectItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)) +SelectItem.displayName = SelectPrimitive.Item.displayName + +const SelectSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SelectSeparator.displayName = SelectPrimitive.Separator.displayName + +export { + Select, + SelectGroup, + SelectValue, + SelectTrigger, + SelectContent, + SelectLabel, + SelectItem, + SelectSeparator +} diff --git a/lib/types.ts b/lib/types.ts index fc2a907..4434987 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -10,4 +10,9 @@ export interface Chat extends Record { sharePath?: string } -export type ServerActionResult = Promise +export type ServerActionResult = Promise< + | Result + | { + error: string + } +> diff --git a/package.json b/package.json index d8ffe5a..00a295f 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@radix-ui/react-dialog": "^1.0.4", "@radix-ui/react-dropdown-menu": "^2.0.5", "@radix-ui/react-label": "^2.0.2", + "@radix-ui/react-select": "^1.2.2", "@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-switch": "^1.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dc8531a..0e14b53 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ dependencies: '@radix-ui/react-label': specifier: ^2.0.2 version: 2.0.2(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-select': + specifier: ^1.2.2 + version: 1.2.2(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-separator': specifier: ^1.0.3 version: 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0) @@ -458,6 +461,12 @@ packages: tslib: 2.5.0 dev: true + /@radix-ui/number@1.0.1: + resolution: {integrity: sha512-T5gIdVO2mmPW3NNhjNgEP3cqMXjXL9UbO0BzWcXfvdBs+BohbQxvd/K5hSVKmn9/lbTdsQVKbUcP5WLCwvUbBg==} + dependencies: + '@babel/runtime': 7.21.5 + dev: false + /@radix-ui/primitive@1.0.1: resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==} dependencies: @@ -897,6 +906,47 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-select@1.2.2(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-zI7McXr8fNaSrUY9mZe4x/HC0jTLY9fWNhO1oLWYMQGDXuV4UCivIGTxwioSzO0ZCYX9iSLyWmAh/1TOmX3Cnw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.21.5 + '@radix-ui/number': 1.0.1 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.6)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.2.6)(react@18.2.0) + '@radix-ui/react-direction': 1.0.1(@types/react@18.2.6)(react@18.2.0) + '@radix-ui/react-dismissable-layer': 1.0.4(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-focus-guards': 1.0.1(@types/react@18.2.6)(react@18.2.0) + '@radix-ui/react-focus-scope': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-id': 1.0.1(@types/react@18.2.6)(react@18.2.0) + '@radix-ui/react-popper': 1.1.2(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-portal': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-slot': 1.0.2(@types/react@18.2.6)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.6)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.6)(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.6)(react@18.2.0) + '@radix-ui/react-use-previous': 1.0.1(@types/react@18.2.6)(react@18.2.0) + '@radix-ui/react-visually-hidden': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0) + '@types/react': 18.2.6 + '@types/react-dom': 18.2.4 + aria-hidden: 1.2.3 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-remove-scroll: 2.5.5(@types/react@18.2.6)(react@18.2.0) + dev: false + /@radix-ui/react-separator@1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-itYmTy/kokS21aiV5+Z56MZB54KrhPgn6eHDKkFeOLR34HMN2s8PaN47qZZAGnvupcjxHaFZnW4pQEh0BvvVuw==} peerDependencies: From 93a7ad99ad98e953c52ca9290f16533c81178c4a Mon Sep 17 00:00:00 2001 From: shadcn Date: Fri, 16 Jun 2023 21:54:32 +0400 Subject: [PATCH 4/4] fix: remove unused hooks --- components/ui/badge.tsx | 22 ++++++++--------- lib/hooks/use-follow-scroll.ts | 43 ---------------------------------- lib/hooks/use-local-storage.ts | 26 -------------------- lib/hooks/use-scroll.ts | 16 ------------- lib/hooks/use-window-size.ts | 39 ------------------------------ 5 files changed, 11 insertions(+), 135 deletions(-) delete mode 100644 lib/hooks/use-follow-scroll.ts delete mode 100644 lib/hooks/use-local-storage.ts delete mode 100644 lib/hooks/use-scroll.ts delete mode 100644 lib/hooks/use-window-size.ts diff --git a/components/ui/badge.tsx b/components/ui/badge.tsx index a0847a1..d9a84b3 100644 --- a/components/ui/badge.tsx +++ b/components/ui/badge.tsx @@ -1,25 +1,25 @@ -import * as React from "react" -import { cva, type VariantProps } from "class-variance-authority" +import * as React from 'react' +import { cva, type VariantProps } from 'class-variance-authority' -import { cn } from "@/lib/utils" +import { cn } from '@/lib/utils' const badgeVariants = cva( - "inline-flex items-center border rounded-full px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + 'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2', { variants: { variant: { default: - "bg-primary hover:bg-primary/80 border-transparent text-primary-foreground", + 'border-transparent bg-primary text-primary-foreground hover:bg-primary/80', secondary: - "bg-secondary hover:bg-secondary/80 border-transparent text-secondary-foreground", + 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80', destructive: - "bg-destructive hover:bg-destructive/80 border-transparent text-destructive-foreground", - outline: "text-foreground", - }, + 'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80', + outline: 'text-foreground' + } }, defaultVariants: { - variant: "default", - }, + variant: 'default' + } } ) diff --git a/lib/hooks/use-follow-scroll.ts b/lib/hooks/use-follow-scroll.ts deleted file mode 100644 index 47a5c29..0000000 --- a/lib/hooks/use-follow-scroll.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { useEffect, useRef } from 'react' - -const scrollToEnd = () => { - window.scrollTo({ - top: document.body.scrollHeight, - left: 0 - }) -} - -export function useFollowScroll(shouldFollow: boolean) { - const shouldFollowRef = useRef(shouldFollow) - const scrollRef = useRef(false) - const triggeredBySelfRef = useRef(false) - - useEffect(() => { - const handleScroll = () => { - if (triggeredBySelfRef.current) { - triggeredBySelfRef.current = false - return - } - scrollRef.current = true - } - window.addEventListener('scroll', handleScroll) - return () => window.removeEventListener('scroll', handleScroll) - }, []) - - useEffect(() => { - if (!scrollRef.current && shouldFollowRef.current) { - setTimeout(() => { - triggeredBySelfRef.current = true - scrollToEnd() - }) - } - }) - - useEffect(() => { - shouldFollowRef.current = shouldFollow - if (!shouldFollow) { - // Reset scrollRef - scrollRef.current = false - } - }, [shouldFollow]) -} diff --git a/lib/hooks/use-local-storage.ts b/lib/hooks/use-local-storage.ts deleted file mode 100644 index 15c4291..0000000 --- a/lib/hooks/use-local-storage.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { useEffect, useState } from 'react' - -const useLocalStorage = ( - key: string, - initialValue: T -): [T, (value: T) => void] => { - const [storedValue, setStoredValue] = useState(initialValue) - - useEffect(() => { - // Retrieve from localStorage - const item = window.localStorage.getItem(key) - if (item) { - setStoredValue(JSON.parse(item)) - } - }, [key]) - - const setValue = (value: T) => { - // Save state - setStoredValue(value) - // Save to localStorage - window.localStorage.setItem(key, JSON.stringify(value)) - } - return [storedValue, setValue] -} - -export default useLocalStorage diff --git a/lib/hooks/use-scroll.ts b/lib/hooks/use-scroll.ts deleted file mode 100644 index 30b841a..0000000 --- a/lib/hooks/use-scroll.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { useCallback, useEffect, useState } from 'react' - -export default function useScroll(threshold: number) { - const [scrolled, setScrolled] = useState(false) - - const onScroll = useCallback(() => { - setScrolled(window.pageYOffset > threshold) - }, [threshold]) - - useEffect(() => { - window.addEventListener('scroll', onScroll) - return () => window.removeEventListener('scroll', onScroll) - }, [onScroll]) - - return scrolled -} diff --git a/lib/hooks/use-window-size.ts b/lib/hooks/use-window-size.ts deleted file mode 100644 index 9a3661d..0000000 --- a/lib/hooks/use-window-size.ts +++ /dev/null @@ -1,39 +0,0 @@ -'use client' - -import { useEffect, useState } from 'react' - -export default function useWindowSize() { - const [windowSize, setWindowSize] = useState<{ - width: number | undefined - height: number | undefined - }>({ - width: undefined, - height: undefined - }) - - useEffect(() => { - // Handler to call on window resize - function handleResize() { - // Set window width/height to state - setWindowSize({ - width: window.innerWidth, - height: window.innerHeight - }) - } - - // Add event listener - window.addEventListener('resize', handleResize) - - // Call handler right away so state gets updated with initial window size - handleResize() - - // Remove event listener on cleanup - return () => window.removeEventListener('resize', handleResize) - }, []) // Empty array ensures that effect is only run on mount - - return { - windowSize, - isMobile: typeof windowSize?.width === 'number' && windowSize?.width < 768, - isDesktop: typeof windowSize?.width === 'number' && windowSize?.width >= 768 - } -}