Skip to content

Commit 7684cc1

Browse files
authored
Merge pull request #82 from SWMTheFirstTake/dev
lg 이상 사이즈에서 채팅방 디자인 변경
2 parents aa0a642 + 1360d48 commit 7684cc1

File tree

10 files changed

+193
-125
lines changed

10 files changed

+193
-125
lines changed

app/chat/[roomId]/page.tsx

Lines changed: 7 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client';
22

3-
import ChatHeader from '@/components/chat/ChatHeader';
3+
import ChatMenu from '@/components/chat/ChatMenu';
44
import ClosetPanel from '@/components/chat/closet/ClosetPanel';
55
import FittingPanel from '@/components/chat/fitting/FittingPanel';
66
import ChatPanel from '@/components/chat/message/ChatPanel';
@@ -36,49 +36,17 @@ export default function ChatRoomPage({ params }: ChatRoomPageProps) {
3636
setTempMessage(null);
3737
}, [roomId, mutate, setTempMessage, tempMessage?.roomId, tempMessage?.userMessage]);
3838

39-
// lg 이상일 때 panel이 'chat'이면 자동으로 'closet'으로 전환
40-
useEffect(() => {
41-
const mq = window.matchMedia('(min-width: 1024px)');
42-
const enforceDesktopPanel = (matches: boolean) => {
43-
if (matches && panel === 'chat') {
44-
setPanel('closet');
45-
}
46-
};
47-
48-
// 초기 상태에서도 한 번 체크
49-
enforceDesktopPanel(mq.matches);
50-
51-
const handler = (e: MediaQueryListEvent) => enforceDesktopPanel(e.matches);
52-
mq.addEventListener('change', handler);
53-
return () => mq.removeEventListener('change', handler);
54-
}, [panel, setPanel]);
5539

5640
return (
5741
<Suspense>
5842
<div className="flex flex-col">
59-
{/* 모바일/태블릿에서는 상단에 헤더 표시 */}
60-
<div className="lg:hidden">
61-
<ChatHeader />
43+
{/* 상단에 메뉴 표시 */}
44+
<div>
45+
<ChatMenu />
6246
</div>
63-
<div className="flex h-[100vh]">
64-
{/* 데스크톱: 좌우 분할 레이아웃 */}
65-
<div className="hidden lg:flex w-full lg:w-1/2 h-full lg:transition-all lg:duration-300">
66-
<ChatPanel />
67-
</div>
68-
69-
<div className="hidden lg:flex flex-col h-full w-full lg:w-1/2 border-l border-navy-200">
70-
<div className="flex-shrink-0">
71-
<ChatHeader />
72-
</div>
73-
<div className="flex-1 min-h-0">
74-
{panel === 'closet' && <ClosetPanel />}
75-
{panel === 'codination' && <CodinationPanel />}
76-
{panel === 'fitting' && <FittingPanel />}
77-
</div>
78-
</div>
79-
80-
{/* 모바일/태블릿: 단일 패널 레이아웃 */}
81-
<div className="lg:hidden w-full h-[calc(100vh-4.1rem)]">
47+
<div className="flex h-[100vh] min-[1440px]:pl-28">
48+
{/* 단일 패널 레이아웃 */}
49+
<div className="w-full h-[calc(100vh-4.1rem)] min-[1440px]:h-[100vh]">
8250
{panel === 'chat' && <ChatPanel />}
8351
{panel === 'closet' && <ClosetPanel />}
8452
{panel === 'codination' && <CodinationPanel />}

app/chat/page.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client';
22

3-
import ChatHeader from '@/components/chat/ChatHeader';
3+
import ChatMenu from '@/components/chat/ChatMenu';
44
import ClosetPanel from '@/components/chat/closet/ClosetPanel';
55
import FittingPanel from '@/components/chat/fitting/FittingPanel';
66
import ChatPanel from '@/components/chat/message/ChatPanel';
@@ -56,19 +56,19 @@ export default function Chat() {
5656
return (
5757
<Suspense>
5858
<div className="flex flex-col">
59-
{/* 모바일/태블릿에서는 상단에 헤더 표시 */}
59+
{/* 모바일/태블릿에서는 상단에 메뉴 표시 */}
6060
<div className="lg:hidden">
61-
<ChatHeader />
61+
<ChatMenu />
6262
</div>
63-
<div className="flex h-[calc(100vh-5rem)] lg:h-[100vh]">
63+
<div className="flex h-[calc(100vh-5rem)] lg:h-[100vh] min-[1440px]:pl-28">
6464
{/* 데스크톱: 좌우 분할 레이아웃 */}
6565
<div className="hidden lg:flex w-full lg:w-1/2 h-full lg:transition-all lg:duration-300">
6666
<ChatPanel />
6767
</div>
6868

6969
<div className="hidden lg:flex flex-col h-full w-full lg:w-1/2 border-l border-navy-200">
7070
<div className="flex-shrink-0">
71-
<ChatHeader />
71+
<ChatMenu />
7272
</div>
7373
<div className="flex-1 min-h-0">
7474
{panel === 'closet' && <ClosetPanel />}
@@ -77,7 +77,7 @@ export default function Chat() {
7777
</div>
7878

7979
{/* 모바일/태블릿: 단일 패널 레이아웃 */}
80-
<div className="lg:hidden w-full h-[calc(100vh-5rem)]">
80+
<div className="lg:hidden w-full h-[calc(100vh-5rem)] min-[1440px]:h-[100vh]">
8181
{panel === 'chat' && <ChatPanel />}
8282
{panel === 'closet' && <ClosetPanel />}
8383
{panel === 'fitting' && <FittingPanel />}

app/layout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,15 @@ export default function RootLayout({
9090
</head>
9191
<body className={`${plus_jakarta_sans.variable} ${noto_sans_kr.variable} antialiased min-h-screen flex flex-col`}>
9292
<main className="flex-grow text-blue dark:text-blue-250">
93+
<div className="w-full max-w-[1024px] mx-auto">
9394
<JotaiProvider>
9495
<QueryProvider>
9596
<ServerAuthProvider>
9697
<StorageInitializer>{children}</StorageInitializer>
9798
</ServerAuthProvider>
9899
</QueryProvider>
99100
</JotaiProvider>
101+
</div>
100102
</main>
101103
<Suspense fallback={null}>
102104
<WebVitals />

src/components/chat/ChatHeader.tsx

Lines changed: 3 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { activeCodinationAtom, panelAtom } from '@/atoms/chatAtoms';
66
import LucideIcon from '@/components/ui/icons/LucideIcon';
77
import SettingsPanel from '@/components/chat/settings/SettingsPanel';
88

9-
export default function ChatHeader() {
9+
export default function ChatMenu() {
1010
const [panel, setPanel] = useAtom(panelAtom);
1111
const setActiveCodination = useSetAtom(activeCodinationAtom);
1212
const [isClient, setIsClient] = useState(false);
@@ -36,54 +36,8 @@ export default function ChatHeader() {
3636

3737
return (
3838
<>
39-
{/* Desktop/Large screens header */}
40-
<header className="hidden lg:flex h-20 items-center justify-between px-4 py-6 mx-16 lg:mx-auto w-[calc(100vw-68px)] lg:w-full bg-white dark:bg-slate-800">
41-
<div className="flex items-center gap-4">
42-
<button
43-
className={`btn cursor-pointer text-blue text-2xl w-32 h-10 flex items-center justify-center ${isClient && panel == 'closet' ? 'font-bold' : ''}`}
44-
onClick={handleCloset}
45-
>
46-
<span className="inline-flex mr-2">
47-
<LucideIcon name={'Shirt'} size={20} />
48-
</span>
49-
옷장
50-
</button>
51-
<button
52-
className={`btn cursor-pointer text-blue text-2xl w-32 h-10 flex items-center justify-center ${isClient && panel == 'codination' ? 'font-bold' : ''}`}
53-
onClick={handleCodination}
54-
>
55-
<span className="inline-flex mr-2">
56-
<LucideIcon name={'Layers'} size={20} />
57-
</span>
58-
코디
59-
</button>
60-
<button
61-
className={`btn cursor-pointer text-blue text-2xl w-32 h-10 flex items-center justify-center ${isClient && panel == 'fitting' ? 'font-bold' : ''}`}
62-
onClick={handleFitting}
63-
>
64-
<span className="inline-flex mr-2">
65-
<LucideIcon name={'ScanLine'} size={20} />
66-
</span>
67-
피팅
68-
</button>
69-
</div>
70-
<div className="flex items-center gap-4">
71-
<SettingsPanel>
72-
<button className="btn cursor-pointer text-blue text-2xl w-32 h-10 flex items-center justify-center relative">
73-
<span className="inline-flex mr-2">
74-
<LucideIcon name={'Settings'} size={20} />
75-
</span>
76-
설정
77-
</button>
78-
</SettingsPanel>
79-
<div className="w-9 h-9 rounded-full bg-gray-200 dark:bg-slate-600 flex items-center justify-center text-gray-500 dark:text-gray-400 font-bold text-lg">
80-
U
81-
</div>
82-
</div>
83-
</header>
84-
85-
{/* Mobile/Small screens bottom fixed nav */}
86-
<nav className="lg:hidden fixed bottom-0 left-0 right-0 z-50 bg-white dark:bg-slate-800 border-t border-slate-200 dark:border-slate-600">
39+
{/* Bottom fixed nav for all screen sizes */}
40+
<nav className="fixed bottom-0 left-0 right-0 z-50 bg-white dark:bg-slate-800 border-t border-slate-200 dark:border-slate-600">
8741
<div className="flex items-center justify-around h-16">
8842
<button
8943
aria-label="Chat"

src/components/chat/ChatMenu.tsx

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
'use client';
2+
3+
import { useAtom, useSetAtom } from 'jotai';
4+
import { useEffect, useState } from 'react';
5+
import { activeCodinationAtom, panelAtom } from '@/atoms/chatAtoms';
6+
import LucideIcon from '@/components/ui/icons/LucideIcon';
7+
import SettingsPanel from '@/components/chat/settings/SettingsPanel';
8+
9+
export default function ChatMenu() {
10+
const [panel, setPanel] = useAtom(panelAtom);
11+
const setActiveCodination = useSetAtom(activeCodinationAtom);
12+
const [isClient, setIsClient] = useState(false);
13+
14+
useEffect(() => {
15+
setIsClient(true);
16+
}, []);
17+
18+
const handleChat = () => {
19+
setPanel('chat');
20+
};
21+
22+
const handleCloset = () => {
23+
setPanel('closet');
24+
setActiveCodination(null);
25+
};
26+
27+
const handleCodination = () => {
28+
setPanel('codination');
29+
setActiveCodination(null);
30+
};
31+
32+
const handleFitting = () => {
33+
setPanel('fitting');
34+
setActiveCodination(null);
35+
};
36+
37+
return (
38+
<>
39+
{/* Bottom fixed nav: visible under 1440px */}
40+
<nav className="fixed bottom-0 left-0 right-0 z-50 bg-white dark:bg-slate-800 border-t border-slate-200 dark:border-slate-600 max-[1439px]:flex min-[1440px]:hidden">
41+
<div className="flex items-center justify-around h-16 w-full">
42+
<button
43+
aria-label="Chat"
44+
className={`w-16 flex flex-col items-center gap-1 ${isClient && panel == 'chat' ? 'font-bold text-blue-600 dark:text-blue-400' : 'text-slate-400 dark:text-slate-600'}`}
45+
onClick={handleChat}
46+
>
47+
<span className="inline-flex">
48+
<LucideIcon name={'MessageSquare'} size={24} />
49+
</span>
50+
<span className="text-xs">채팅</span>
51+
</button>
52+
<button
53+
aria-label="Closet"
54+
className={`w-16 flex flex-col items-center gap-1 ${isClient && panel == 'closet' ? 'font-bold text-blue-600 dark:text-blue-400' : 'text-slate-400 dark:text-slate-600'}`}
55+
onClick={handleCloset}
56+
>
57+
<span className="inline-flex">
58+
<LucideIcon name={'Shirt'} size={24} />
59+
</span>
60+
<span className="text-xs">옷장</span>
61+
</button>
62+
<button
63+
aria-label="Codination"
64+
className={`w-16 flex flex-col items-center gap-1 ${isClient && panel == 'codination' ? 'font-bold text-blue-600 dark:text-blue-400' : 'text-slate-400 dark:text-slate-600'}`}
65+
onClick={handleCodination}
66+
>
67+
<span className="inline-flex">
68+
<LucideIcon name={'Layers'} size={24} />
69+
</span>
70+
<span className="text-xs">코디</span>
71+
</button>
72+
<button
73+
aria-label="Fitting"
74+
className={`w-16 flex flex-col items-center gap-1 ${isClient && panel == 'fitting' ? 'font-bold text-blue-600 dark:text-blue-400' : 'text-slate-400 dark:text-slate-600'}`}
75+
onClick={handleFitting}
76+
>
77+
<span className="inline-flex">
78+
<LucideIcon name={'ScanLine'} size={24} />
79+
</span>
80+
<span className="text-xs">피팅</span>
81+
</button>
82+
<SettingsPanel>
83+
<button
84+
aria-label="Settings"
85+
className="w-16 flex flex-col items-center gap-1 text-slate-400 dark:text-slate-600"
86+
>
87+
<span className="inline-flex">
88+
<LucideIcon name={'Settings'} size={24} />
89+
</span>
90+
<span className="text-xs">설정</span>
91+
</button>
92+
</SettingsPanel>
93+
</div>
94+
</nav>
95+
96+
{/* Left vertical sidebar: visible at 1440px and above */}
97+
<aside className="fixed top-0 left-0 bottom-0 z-50 w-28 bg-white dark:bg-slate-800 border-r border-slate-200 dark:border-slate-600 min-[1440px]:flex max-[1439px]:hidden">
98+
<div className="flex flex-col items-center justify-center gap-10 w-full h-full">
99+
<button
100+
aria-label="Chat"
101+
className={`w-full flex flex-col items-center gap-1 ${isClient && panel == 'chat' ? 'font-bold text-blue-600 dark:text-blue-400' : 'text-slate-400 dark:text-slate-600'}`}
102+
onClick={handleChat}
103+
>
104+
<span className="inline-flex">
105+
<LucideIcon name={'MessageSquare'} size={24} />
106+
</span>
107+
<span className="text-[10px]">채팅</span>
108+
</button>
109+
<button
110+
aria-label="Closet"
111+
className={`w-full flex flex-col items-center gap-1 ${isClient && panel == 'closet' ? 'font-bold text-blue-600 dark:text-blue-400' : 'text-slate-400 dark:text-slate-600'}`}
112+
onClick={handleCloset}
113+
>
114+
<span className="inline-flex">
115+
<LucideIcon name={'Shirt'} size={24} />
116+
</span>
117+
<span className="text-[10px]">옷장</span>
118+
</button>
119+
<button
120+
aria-label="Codination"
121+
className={`w-full flex flex-col items-center gap-1 ${isClient && panel == 'codination' ? 'font-bold text-blue-600 dark:text-blue-400' : 'text-slate-400 dark:text-slate-600'}`}
122+
onClick={handleCodination}
123+
>
124+
<span className="inline-flex">
125+
<LucideIcon name={'Layers'} size={24} />
126+
</span>
127+
<span className="text-[10px]">코디</span>
128+
</button>
129+
<button
130+
aria-label="Fitting"
131+
className={`w-full flex flex-col items-center gap-1 ${isClient && panel == 'fitting' ? 'font-bold text-blue-600 dark:text-blue-400' : 'text-slate-400 dark:text-slate-600'}`}
132+
onClick={handleFitting}
133+
>
134+
<span className="inline-flex">
135+
<LucideIcon name={'ScanLine'} size={24} />
136+
</span>
137+
<span className="text-[10px]">피팅</span>
138+
</button>
139+
<SettingsPanel>
140+
<button
141+
aria-label="Settings"
142+
className="w-full flex flex-col items-center gap-1 text-slate-400 dark:text-slate-600"
143+
>
144+
<span className="inline-flex">
145+
<LucideIcon name={'Settings'} size={24} />
146+
</span>
147+
<span className="text-[10px]">설정</span>
148+
</button>
149+
</SettingsPanel>
150+
</div>
151+
</aside>
152+
</>
153+
);
154+
}

src/components/chat/closet/ClosetClothCard.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22

33
import Image from 'next/image';
44
import ClothModal from '@/components/chat/modal/ClothModal';
5+
import LucideIcon from '@/components/ui/icons/LucideIcon';
6+
import { useCloset } from '@/hooks/useCloset';
57

68
interface ClosetClothCardProps {
79
cloth: ClosetCloth;
810
}
911

1012
export default function ClosetClothCard({ cloth }: ClosetClothCardProps) {
13+
const { removeClothFromCloset } = useCloset();
1114
// ClosetCloth를 Product 타입으로 변환
1215
const product: Product = {
1316
product_id: cloth.id,
@@ -16,6 +19,18 @@ export default function ClosetClothCard({ cloth }: ClosetClothCardProps) {
1619

1720
return (
1821
<div className="aspect-[3/4] flex flex-col relative border-2 transition-all duration-200 hover:shadow-md group dark:bg-blue-800">
22+
{/* 삭제 버튼 (우상단) */}
23+
<button
24+
aria-label="remove-from-closet"
25+
className="absolute top-2 right-2 z-10 w-8 h-8 rounded-full bg-white/90 dark:bg-slate-700 text-slate-600 dark:text-slate-200 shadow hover:bg-white dark:hover:bg-slate-600 flex items-center justify-center"
26+
onClick={(e) => {
27+
e.preventDefault();
28+
e.stopPropagation();
29+
removeClothFromCloset(cloth.id);
30+
}}
31+
>
32+
<LucideIcon name={'X'} size={16} />
33+
</button>
1934
{/* ClothModal로 감싸기 */}
2035
<ClothModal product={product}>
2136
<div className="h-full flex flex-col cursor-pointer">

src/components/chat/fitting/FittingDetail.tsx

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ interface ClosetCloth {
1616
export default function FittingDetail() {
1717
const [activeCodination, setActiveCodination] = useAtom(activeCodinationAtom);
1818
const user = useAtomValue(userAtom);
19-
19+
2020
return (
2121
<div className="flex flex-col space-y-4 h-full">
2222
{/* 모델 이미지 정보 */}
@@ -68,12 +68,7 @@ export default function FittingDetail() {
6868
? activeCodination.cloths.map((cloth: ClosetCloth, index: number) => (
6969
<div key={index} className="flex items-center gap-3 p-3 rounded-lg">
7070
<div className="flex-shrink-0 flex items-center justify-center w-15 aspect-[3/4]">
71-
<Image
72-
src={cloth.url}
73-
alt={cloth.name}
74-
fill
75-
className="rounded-lg object-cover"
76-
/>
71+
<Image src={cloth.url} alt={cloth.name} fill className="rounded-lg object-cover" />
7772
</div>
7873
{/* 텍스트 영역 */}
7974
<div className="flex-1 min-w-0">

0 commit comments

Comments
 (0)