Skip to content

Commit 778c60c

Browse files
authored
feat: 프로픨 계정 메뉴 드롭다운 추가
1 parent 61b9f64 commit 778c60c

1 file changed

Lines changed: 204 additions & 5 deletions

File tree

src/app/components/Layout.tsx

Lines changed: 204 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
1-
import { Link, Outlet, useLocation } from "react-router";
2-
import { Menu, UserRound, X } from "lucide-react";
1+
import { Link, Outlet, useLocation, useNavigate } from "react-router";
2+
import { Github, LogOut, Menu, Settings, UserRound, UsersRound, X } from "lucide-react";
33
import { useEffect, useState } from "react";
44
import { motion } from "motion/react";
55
import { CodeDockWordmark } from "./CodeDockWordmark";
66
import { CoffeeLogo } from "./CoffeeLogo";
77
import { Footer } from "./Footer";
88
import { LanguageToggleButton } from "./LanguageToggleButton";
99
import { ThemeToggleButton } from "./ThemeToggleButton";
10+
import {
11+
DropdownMenu,
12+
DropdownMenuContent,
13+
DropdownMenuItem,
14+
DropdownMenuLabel,
15+
DropdownMenuSeparator,
16+
DropdownMenuTrigger,
17+
} from "./ui/dropdown-menu";
1018
import { useTheme } from "../contexts/ThemeContext";
1119

1220
const navItems = [
@@ -24,11 +32,15 @@ const currentUser = {
2432

2533
export function Layout() {
2634
const location = useLocation();
35+
const navigate = useNavigate();
2736
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
2837
const [remoteNav, setRemoteNav] = useState(false);
2938
const { colors } = useTheme();
3039

3140
const isActive = (path: string) => location.pathname === path;
41+
const handleLogout = () => {
42+
navigate("/login");
43+
};
3244

3345
useEffect(() => {
3446
const handleScroll = () => {
@@ -138,9 +150,12 @@ export function Layout() {
138150
>
139151
<ThemeToggleButton />
140152

153+
<AccountMenu variant="full" onLogout={handleLogout} />
154+
<AccountMenu variant="icon" onLogout={handleLogout} />
155+
141156
<Link
142157
to="/profile"
143-
className="hidden h-12 items-center gap-3 rounded-2xl px-3 no-underline transition-all hover:scale-[1.02] xl:flex"
158+
className="hidden"
144159
style={{
145160
background: `linear-gradient(135deg, ${colors.primary}, 0.12), rgba(234, 247, 255, 0.045))`,
146161
border: `1px solid ${colors.primary}, 0.22)`,
@@ -165,7 +180,7 @@ export function Layout() {
165180

166181
<Link
167182
to="/profile"
168-
className="grid h-10 w-10 place-items-center rounded-xl no-underline transition-all hover:scale-110 xl:hidden"
183+
className="hidden"
169184
style={{
170185
background: `${colors.primary}, 0.10)`,
171186
border: `1px solid ${colors.primary}, 0.22)`,
@@ -199,9 +214,10 @@ export function Layout() {
199214
transform: remoteNav ? "translateY(0) scale(1)" : "translateY(-8px) scale(0.96)",
200215
}}
201216
>
217+
<AccountMenu variant="compact" onLogout={handleLogout} tabIndex={remoteNav ? 0 : -1} />
202218
<Link
203219
to="/profile"
204-
className="hidden h-12 items-center gap-2 rounded-full px-3 no-underline lg:flex"
220+
className="hidden"
205221
style={{
206222
background: `linear-gradient(135deg, ${colors.primary}, 0.12), rgba(234, 247, 255, 0.045))`,
207223
border: `1px solid ${colors.primary}, 0.22)`,
@@ -240,6 +256,39 @@ export function Layout() {
240256
</span>
241257
</span>
242258
</Link>
259+
<HeaderLink
260+
item={{ path: "/settings", label: "계정 설정" }}
261+
active={isActive("/settings")}
262+
onClick={() => setMobileMenuOpen(false)}
263+
/>
264+
<HeaderLink
265+
item={{ path: "/profile", label: "GitHub 연동 관리" }}
266+
active={false}
267+
onClick={() => setMobileMenuOpen(false)}
268+
/>
269+
<HeaderLink
270+
item={{ path: "/chat", label: "워크스페이스 / 팀 관리" }}
271+
active={isActive("/chat")}
272+
onClick={() => setMobileMenuOpen(false)}
273+
/>
274+
<button
275+
type="button"
276+
onClick={() => {
277+
setMobileMenuOpen(false);
278+
handleLogout();
279+
}}
280+
className="relative rounded-full px-4 py-2 text-left tracking-tight transition-colors"
281+
style={{
282+
background: "transparent",
283+
border: "none",
284+
color: "#FF6B6B",
285+
fontSize: "14px",
286+
fontWeight: 800,
287+
cursor: "pointer",
288+
}}
289+
>
290+
로그아웃
291+
</button>
243292
{navItems.map((item) => (
244293
<HeaderLink
245294
key={item.path}
@@ -261,6 +310,156 @@ export function Layout() {
261310
);
262311
}
263312

313+
interface AccountMenuProps {
314+
variant: "full" | "icon" | "compact";
315+
onLogout: () => void;
316+
tabIndex?: number;
317+
}
318+
319+
function AccountMenu({ variant, onLogout, tabIndex }: AccountMenuProps) {
320+
const { colors } = useTheme();
321+
const isFull = variant === "full";
322+
const isCompact = variant === "compact";
323+
324+
return (
325+
<DropdownMenu>
326+
<DropdownMenuTrigger asChild>
327+
<button
328+
type="button"
329+
className={
330+
isFull
331+
? "hidden h-12 items-center gap-3 rounded-2xl px-3 transition-all hover:scale-[1.02] xl:flex"
332+
: isCompact
333+
? "hidden h-12 items-center gap-2 rounded-full px-3 transition-all hover:scale-[1.02] lg:flex"
334+
: "grid h-10 w-10 place-items-center rounded-xl transition-all hover:scale-110 xl:hidden"
335+
}
336+
style={
337+
isFull
338+
? {
339+
background: `linear-gradient(135deg, ${colors.primary}, 0.12), rgba(234, 247, 255, 0.045))`,
340+
border: `1px solid ${colors.primary}, 0.22)`,
341+
color: "var(--white)",
342+
boxShadow: `0 0 24px ${colors.primary}, 0.10), inset 0 1px 0 rgba(255, 255, 255, 0.10)`,
343+
backdropFilter: "blur(16px) saturate(180%)",
344+
cursor: "pointer",
345+
}
346+
: isCompact
347+
? {
348+
background: `linear-gradient(135deg, ${colors.primary}, 0.12), rgba(234, 247, 255, 0.045))`,
349+
border: `1px solid ${colors.primary}, 0.22)`,
350+
color: "var(--white)",
351+
boxShadow: `0 18px 55px rgba(0, 0, 0, 0.38), 0 0 24px ${colors.primary}, 0.10)`,
352+
backdropFilter: "blur(22px) saturate(190%)",
353+
WebkitBackdropFilter: "blur(22px) saturate(190%)",
354+
cursor: "pointer",
355+
}
356+
: {
357+
background: `${colors.primary}, 0.10)`,
358+
border: `1px solid ${colors.primary}, 0.22)`,
359+
color: colors.primaryHex,
360+
cursor: "pointer",
361+
}
362+
}
363+
aria-label="계정 메뉴 열기"
364+
title={`${currentUser.name} - ${currentUser.email}`}
365+
tabIndex={tabIndex}
366+
>
367+
{variant === "icon" ? (
368+
<UserRound size={19} strokeWidth={2.4} />
369+
) : (
370+
<>
371+
<AccountAvatar />
372+
<span className="grid min-w-0 leading-none">
373+
<span
374+
className={
375+
isCompact
376+
? "max-w-[92px] truncate text-sm font-black tracking-tight"
377+
: "max-w-[112px] truncate text-sm font-black tracking-tight"
378+
}
379+
>
380+
{currentUser.name}
381+
</span>
382+
{isFull && (
383+
<span
384+
className="mt-1 max-w-[112px] truncate text-[11px] font-bold tracking-tight"
385+
style={{ color: "rgba(234, 247, 255, 0.62)" }}
386+
>
387+
{currentUser.workspace}
388+
</span>
389+
)}
390+
</span>
391+
</>
392+
)}
393+
</button>
394+
</DropdownMenuTrigger>
395+
<DropdownMenuContent
396+
align="end"
397+
sideOffset={10}
398+
className="w-64 rounded-2xl p-2"
399+
style={{
400+
background: "rgba(5, 11, 20, 0.96)",
401+
border: `1px solid ${colors.primary}, 0.22)`,
402+
color: "var(--white)",
403+
boxShadow: `0 18px 55px rgba(0, 0, 0, 0.45), 0 0 28px ${colors.primary}, 0.10)`,
404+
backdropFilter: "blur(22px) saturate(180%)",
405+
}}
406+
>
407+
<DropdownMenuLabel className="px-3 py-2">
408+
<span className="block truncate text-sm font-black tracking-tight">{currentUser.name}</span>
409+
<span className="mt-1 block truncate text-xs font-bold tracking-tight" style={{ color: "var(--muted)" }}>
410+
{currentUser.email}
411+
</span>
412+
</DropdownMenuLabel>
413+
<DropdownMenuSeparator style={{ background: `${colors.primary}, 0.14)` }} />
414+
<AccountMenuLink to="/profile" icon={UserRound} label="프로필 보기" />
415+
<AccountMenuLink to="/settings" icon={Settings} label="계정 설정" />
416+
<AccountMenuLink to="/profile" icon={Github} label="GitHub 연동 관리" />
417+
<AccountMenuLink to="/chat" icon={UsersRound} label="워크스페이스 / 팀 관리" />
418+
<DropdownMenuSeparator style={{ background: `${colors.primary}, 0.14)` }} />
419+
<DropdownMenuItem
420+
onSelect={onLogout}
421+
className="rounded-xl px-3 py-2.5 tracking-tight"
422+
style={{
423+
color: "#FF6B6B",
424+
cursor: "pointer",
425+
fontSize: "14px",
426+
fontWeight: 900,
427+
}}
428+
>
429+
<LogOut size={17} strokeWidth={2.2} />
430+
로그아웃
431+
</DropdownMenuItem>
432+
</DropdownMenuContent>
433+
</DropdownMenu>
434+
);
435+
}
436+
437+
interface AccountMenuLinkProps {
438+
to: string;
439+
icon: typeof UserRound;
440+
label: string;
441+
}
442+
443+
function AccountMenuLink({ to, icon: Icon, label }: AccountMenuLinkProps) {
444+
return (
445+
<DropdownMenuItem asChild className="rounded-xl px-3 py-2.5 tracking-tight">
446+
<Link
447+
to={to}
448+
className="flex items-center gap-2 no-underline"
449+
style={{
450+
color: "var(--white)",
451+
cursor: "pointer",
452+
fontSize: "14px",
453+
fontWeight: 900,
454+
}}
455+
>
456+
<Icon size={17} strokeWidth={2.2} />
457+
{label}
458+
</Link>
459+
</DropdownMenuItem>
460+
);
461+
}
462+
264463
function AccountAvatar() {
265464
const { colors } = useTheme();
266465

0 commit comments

Comments
 (0)