Skip to content

Commit 13ef808

Browse files
authored
Merge pull request #310 from boostcampwm2025/dev
FE 개선사항 배포
2 parents 517eccd + f5a1c44 commit 13ef808

32 files changed

Lines changed: 569 additions & 139 deletions

apps/api/src/modules/noti/noti.controller.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,21 @@ export class NotiController {
2020
return await this.notiService.getNotisByUserId(userId);
2121
}
2222

23+
@UseGuards(AuthGuard)
24+
@Patch()
25+
async readAll(@UserId() userId: string) {
26+
await this.notiService.readAllNotis(userId);
27+
return { ok: true };
28+
}
29+
30+
@UseGuards(AuthGuard)
31+
@Delete()
32+
async deleteAll(@UserId() userId: string) {
33+
await this.notiService.deleteAllNotis(userId);
34+
35+
return { ok: true };
36+
}
37+
2338
@UseGuards(AuthGuard)
2439
@Patch(':id')
2540
async readNoti(@UserId() userId: string, @Param('id') notiId: string) {

apps/api/src/modules/noti/noti.service.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,13 @@ export class NotiService {
133133
await this.notiRepo.update({ id: notiId }, { isRead: true });
134134
}
135135

136+
async readAllNotis(userId: string): Promise<void> {
137+
await this.notiRepo.update(
138+
{ receiver: { id: userId }, isRead: false },
139+
{ isRead: true },
140+
);
141+
}
142+
136143
async deleteNoti(userId: string, notiId: string): Promise<void> {
137144
const noti = await this.notiRepo.findOne({
138145
where: { id: notiId },
@@ -147,6 +154,10 @@ export class NotiService {
147154
await this.notiRepo.delete({ id: notiId });
148155
}
149156

157+
async deleteAllNotis(userId: string): Promise<void> {
158+
await this.notiRepo.delete({ receiver: { id: userId } });
159+
}
160+
150161
private async toNotiResponseDto(noti: Noti): Promise<NotiResponseDto> {
151162
// imgUrl - related의 img 없으면 null
152163
// like, comment - related: post

apps/web/app/globals.css

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,23 @@
2828
}
2929
}
3030

31+
/* ⚠️ 리캡 기능 구현 이후 삭제 예정 - 리캡 '준비중' 둥실 안내용 */
32+
@keyframes float-up {
33+
from {
34+
transform: translateY(0);
35+
opacity: 1;
36+
}
37+
to {
38+
transform: translateY(-24px);
39+
opacity: 0;
40+
}
41+
}
42+
3143
@theme {
3244
--animate-slide-up: slide-up 0.3s ease-out;
3345
--animate-fade-in: fade-in 0.2s ease-out;
3446
--animate-slide-in-right: slide-in-right 0.3s ease-out;
47+
--animate-float-up: float-up 1.8s ease-out forwards; /* ⚠️ 리캡 기능 구현 이후 삭제 예정 */
3548

3649
--color-primary: #00214d;
3750
--color-accent-cyan: #00ebc7;
@@ -52,7 +65,7 @@
5265
}
5366

5467
@utility sidebar-icon {
55-
@apply w-6 aspect-square;
68+
@apply w-6 aspect-square shrink-0;
5669
}
5770

5871
* {

apps/web/app/layout.tsx

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Metadata, Viewport } from 'next';
22
import './globals.css';
3-
import { Header, Sidebar, RightPanel, ModalContainer, LoadingSpinner, MobileBottomNav } from '@/components';
3+
import { Header, Sidebar, ResizableRightPanel, ModalContainer, LoadingSpinner, MobileBottomNav } from '@/components';
44
import MobileNotiOverlay from '@/components/layout/MobileNotiOverlay';
55
import { Suspense } from 'react';
66
import NotiPollingGate from '@/components/noti/NotiPollingGate';
@@ -31,8 +31,6 @@ export const viewport: Viewport = {
3131
themeColor: '#111111',
3232
};
3333

34-
const MINI_PLAYER_BAR_HEIGHT_HEIGHT = 'h-16';
35-
3634
export default function RootLayout({
3735
children,
3836
}: Readonly<{
@@ -60,7 +58,7 @@ export default function RootLayout({
6058
<NotiPollingGate />
6159

6260
<ToastProvider>
63-
<div className="flex h-screen">
61+
<div className="flex h-dvh overflow-hidden">
6462
{/* 좌측 사이드바 (데스크탑 전용) */}
6563
<div className="hidden lg:flex h-full">
6664
<Sidebar />
@@ -74,12 +72,8 @@ export default function RootLayout({
7472
<main className="flex-1 overflow-y-auto min-w-0">{children}</main>
7573
</div>
7674

77-
{/* 플레이어: 모바일 하단 스트립 / 데스크탑 우측 패널 */}
78-
<aside
79-
className={`relative flex-shrink-0 min-w-0 w-full ${MINI_PLAYER_BAR_HEIGHT_HEIGHT} border-t-1 border-primary lg:w-95 lg:h-full lg:border-t-0 lg:border-l-2`}
80-
>
81-
<RightPanel />
82-
</aside>
75+
{/* 플레이어: 모바일 하단 스트립 / 데스크탑 우측 패널 (너비 드래그 조절) */}
76+
<ResizableRightPanel />
8377

8478
{/* 모바일 하단 네비게이션 (flex 흐름 안에서 자연스럽게 맨 아래) */}
8579
<MobileBottomNav />

apps/web/src/api/internal/noti.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,11 @@ export async function fetchNotis() {
88
export async function markNotiRead(notiId: string) {
99
await internalClient.patch(`/noti/${notiId}`);
1010
}
11+
12+
export async function markAllNotiRead() {
13+
await internalClient.patch('/noti');
14+
}
15+
16+
export async function deleteAllNotis() {
17+
await internalClient.delete('/noti');
18+
}

apps/web/src/components/ConfirmOverlay.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export default function ConfirmOverlay({
3838
if (!open) return null;
3939

4040
const overlay = (
41-
<div className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/30" onClick={onCancel}>
41+
<div data-drawer-keep className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/30" onClick={onCancel}>
4242
<div
4343
ref={ref}
4444
className="w-[280px] bg-white border-2 border-primary rounded-lg shadow-[4px_4px_0px_0px_#00214D] p-4"

apps/web/src/components/archive/ArchiveViewHeader.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { createNewPlaylist } from '@/api';
22
import { usePlaylistRefreshStore } from '@/stores';
3-
import { PlusCircle } from 'lucide-react';
3+
import { Plus } from 'lucide-react';
44
import { toast } from 'react-toastify';
55

66
export default function ArchiveViewHeader() {
@@ -27,8 +27,8 @@ export default function ArchiveViewHeader() {
2727
className="mt-4 md:mt-0 flex items-center bg-primary text-white px-6 py-3 rounded-xl font-bold hover:bg-accent-pink hover:shadow-[4px_4px_0px_0px_#00EBC7] transition-all border-2 border-transparent hover:border-black"
2828
onClick={onCreateNewPlaylist}
2929
>
30-
<PlusCircle className="w-5 h-5 mr-2" />
31-
<span>즉시 추가하기</span>
30+
<Plus className="w-5 h-5 mr-2" />
31+
<span>플레이리스트 추가</span>
3232
</button>
3333
</div>
3434
);

apps/web/src/components/layout/Header.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export default function Header() {
2323
};
2424

2525
return (
26-
<header className="sticky top-0 z-10 bg-white/95 backdrop-blur-sm border-b-2 border-primary px-6 py-4 flex items-center justify-between">
26+
<header className="sticky top-0 z-10 bg-white/95 backdrop-blur-sm border-b-2 border-primary px-6 h-16 flex items-center justify-between">
2727
<button type="button" onClick={handleLogoClick} className="text-2xl font-black italic tracking-tighter text-primary uppercase">
2828
VIBR
2929
</button>

apps/web/src/components/layout/MobileBottomNav.tsx

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

33
import React, { useState, useCallback, lazy, Suspense, useEffect } from 'react';
44
import { usePathname, useRouter } from 'next/navigation';
5-
import { Home, Box, PlusCircle, Search, User } from 'lucide-react';
5+
import { Home, Box, Plus, Search, User } from 'lucide-react';
66

77
import { SidebarItemType } from '@/types';
88
import { useModalStore, MODAL_TYPES, useAuthStore } from '@/stores';
@@ -21,7 +21,7 @@ type NavItem = {
2121
const navItems: NavItem[] = [
2222
{ key: SidebarItemType.HOME, icon: Home, label: '홈' },
2323
{ key: SidebarItemType.ARCHIVE, icon: Box, label: '보관함' },
24-
{ key: 'create', icon: PlusCircle, label: '생성', special: true },
24+
{ key: 'create', icon: Plus, label: '추천', special: true },
2525
{ key: SidebarItemType.SEARCH, icon: Search, label: '검색' },
2626
{ key: SidebarItemType.PROFILE, icon: User, label: '프로필' },
2727
];
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
'use client';
2+
3+
type ResizeHandleProps = {
4+
onPointerDown: (e: React.PointerEvent) => void;
5+
/** 핸들이 붙는 가장자리 */
6+
side: 'left' | 'right';
7+
isDragging?: boolean;
8+
};
9+
10+
/**
11+
* 패널 가장자리에 놓이는 얇은 세로 드래그 바.
12+
* 로직은 없고 onPointerDown만 부모(useResizable)로 전달한다.
13+
*/
14+
export default function ResizeHandle({ onPointerDown, side, isDragging }: ResizeHandleProps) {
15+
return (
16+
<div
17+
onPointerDown={onPointerDown}
18+
role="separator"
19+
aria-orientation="vertical"
20+
className={`
21+
absolute top-0 ${side === 'right' ? '-right-1' : '-left-1'} w-2 h-full z-50
22+
cursor-col-resize transition-colors
23+
${isDragging ? 'bg-primary/30' : 'hover:bg-primary/20'}
24+
`}
25+
/>
26+
);
27+
}

0 commit comments

Comments
 (0)