Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
},
"dependencies": {
"@hookform/resolvers": "^5.2.2",
"@radix-ui/react-alert-dialog": "^1.1.15",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-separator": "^1.1.7",
Expand Down
38 changes: 38 additions & 0 deletions src/app/(with-layout)/_shared/components/logout-alert-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle
} from "@/components/ui/alert-dialog";

interface LogoutAlertDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
onConfirm: () => void | Promise<void>;
loading?: boolean;
}

export default function LogoutAlertDialog(props: LogoutAlertDialogProps) {
return (
<AlertDialog open={props.open} onOpenChange={props.onOpenChange}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>로그아웃 하시겠습니까?</AlertDialogTitle>
<AlertDialogDescription>
로그아웃 후 로그인 페이지로 이동합니다.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={props.loading}>취소</AlertDialogCancel>
<AlertDialogAction onClick={props.onConfirm} disabled={props.loading}>
{props.loading ? "로그아웃 중..." : "로그아웃"}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
};
107 changes: 64 additions & 43 deletions src/components/common/app-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import Link from "next/link";
import { Button } from "@/components/ui/button";
import { logout } from "@/app/(with-layout)/_shared/services/logout-api";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { useCallback, useState } from "react";
import LogoutAlertDialog from "@/app/(with-layout)/_shared/components/logout-alert-dialog";

// Menu items.
const items = [
Expand All @@ -42,8 +43,18 @@ const items = [
export default function AppSidebar() {
const router = useRouter();
const [loading, setLoading] = useState<boolean>(false);
const [dialogOpen, setDialogOpen] = useState<boolean>(false);

const onClickLogout = async () => {
// 로그아웃 버튼 클릭 시 다이얼로그 오픈
const onRequestLogout = useCallback(() => {
if (loading) {
return;
}
setDialogOpen(true);
}, [loading])

// 로그아웃 수행
const onConfirmLogout = useCallback(async () => {
if (loading) {
return;
}
Expand All @@ -52,52 +63,62 @@ export default function AppSidebar() {
await logout();
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
setDialogOpen(false);
router.replace('/login');
} finally {
setLoading(false);
}
}
}, [loading, router]);

return (
<Sidebar>
<SidebarHeader>
<div className="flex flex-row items-center gap-5">
<Image src="icons/logo.svg" alt="logo" width="50" height="50" />
<div className="text-2xl font-bold">RomRom</div>
</div>
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>RomRom</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{items.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton asChild>
<Link href={item.url}>
<item.icon />
<span>{item.title}</span>
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<SidebarMenuItem key="로그아웃">
<SidebarMenuButton asChild>
<Button
className="hover:cursor-pointer"
onClick={onClickLogout}
disabled={loading}
>
{loading ? "로그아웃 중..." : "로그아웃"}
</Button>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarFooter>
</Sidebar>
<>
<Sidebar>
<SidebarHeader>
<div className="flex flex-row items-center gap-5">
<Image src="/icons/logo.svg" alt="logo" width="50" height="50" />
<div className="text-2xl font-bold">RomRom</div>
</div>
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>RomRom</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{items.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton asChild>
<Link href={item.url}>
<item.icon />
<span>{item.title}</span>
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<SidebarMenuItem key="로그아웃">
<SidebarMenuButton asChild>
<Button
className="hover:cursor-pointer"
onClick={onRequestLogout}
disabled={loading}
>
{loading ? "로그아웃 중..." : "로그아웃"}
</Button>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarFooter>
</Sidebar>

<LogoutAlertDialog
open={dialogOpen}
onOpenChange={setDialogOpen}
onConfirm={onConfirmLogout}
loading={loading}
/>
</>
);
};
157 changes: 157 additions & 0 deletions src/components/ui/alert-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
"use client"

import * as React from "react"
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"

import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"

function AlertDialog({
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />
}

function AlertDialogTrigger({
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
return (
<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
)
}

function AlertDialogPortal({
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
return (
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
)
}

function AlertDialogOverlay({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) {
return (
<AlertDialogPrimitive.Overlay
data-slot="alert-dialog-overlay"
className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
className
)}
{...props}
/>
)
}

function AlertDialogContent({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Content>) {
return (
<AlertDialogPortal>
<AlertDialogOverlay />
<AlertDialogPrimitive.Content
data-slot="alert-dialog-content"
className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
className
)}
{...props}
/>
</AlertDialogPortal>
)
}

function AlertDialogHeader({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-dialog-header"
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
{...props}
/>
)
}

function AlertDialogFooter({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-dialog-footer"
className={cn(
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
className
)}
{...props}
/>
)
}

function AlertDialogTitle({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
return (
<AlertDialogPrimitive.Title
data-slot="alert-dialog-title"
className={cn("text-lg font-semibold", className)}
{...props}
/>
)
}

function AlertDialogDescription({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
return (
<AlertDialogPrimitive.Description
data-slot="alert-dialog-description"
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
)
}

function AlertDialogAction({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Action>) {
return (
<AlertDialogPrimitive.Action
className={cn(buttonVariants(), className)}
{...props}
/>
)
}

function AlertDialogCancel({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) {
return (
<AlertDialogPrimitive.Cancel
className={cn(buttonVariants({ variant: "outline" }), className)}
{...props}
/>
)
}

export {
AlertDialog,
AlertDialogPortal,
AlertDialogOverlay,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogHeader,
AlertDialogFooter,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogAction,
AlertDialogCancel,
}