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
18 changes: 0 additions & 18 deletions .claude/settings.local.json

This file was deleted.

3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@
!.yarn/releases
!.yarn/versions


.claude/settings.local.json

# testing
/coverage

Expand Down
10 changes: 9 additions & 1 deletion src/components/admin/layout/LNB.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { useState } from 'react';
import { useEffect, useState } from 'react';
import { useParams, usePathname, useRouter } from 'next/navigation';
import { AdminForumIcon, AdminCalendarIcon, AdminSettingIcon } from '@/assets/icons/admin';
import { CheckRoundIcon, ExitIcon, PeopleIcon } from '@/assets/icons';
Expand All @@ -11,6 +11,7 @@ import {
AlertDialogCancel,
TooltipProvider,
} from '@/components/ui';
import { useMediaQuery } from '@/hooks';
import { cn } from '@/lib/cn';
import { LNBHeader } from '@/components/admin/layout/LNBHeader';
import { LNBClubInfo } from '@/components/admin/layout/LNBClubInfo';
Expand All @@ -23,9 +24,16 @@ function LNB() {
const pathname = usePathname();
const router = useRouter();
const { clubId } = useParams<{ clubId: string }>();
// tablet(696px) 미만에서는 기본적으로 접힌 상태로 시작
const isBelowTablet = useMediaQuery('(max-width: 695.98px)');
const [collapsed, setCollapsed] = useState(false);
const [serviceDialogOpen, setServiceDialogOpen] = useState(false);

// 브레이크포인트를 넘나들 때 기본 접힘 상태를 동기화
useEffect(() => {
setCollapsed(isBelowTablet);
}, [isBelowTablet]);

const servicePath = `/${clubId}/home`;

const managementNavItems = [
Expand Down
6 changes: 3 additions & 3 deletions src/components/admin/schedule/general/ScheduleItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ function ScheduleItem({
return (
<div
className={cn(
'border-line flex items-start gap-600 px-500 py-400',
'border-line tablet:gap-600 tablet:px-500 tablet:py-400 flex items-start gap-400 px-400 py-300',
!isLast && 'border-b',
className,
)}
Expand All @@ -40,7 +40,7 @@ function ScheduleItem({
</div>

{/* Content column */}
<div className="flex flex-1 flex-col justify-center gap-200 self-stretch">
<div className="flex min-w-0 flex-1 flex-col justify-center gap-200 self-stretch">
<span className="typo-sub3 text-text-strong">{schedule.title}</span>
<div className="flex flex-wrap items-center gap-200">
<ScheduleTag variant="type">{SCHEDULE_TYPE_LABEL[schedule.type]}</ScheduleTag>
Expand All @@ -50,7 +50,7 @@ function ScheduleItem({
</div>

{/* Action buttons */}
<div className="flex shrink-0 items-center gap-200">
<div className="tablet:flex-row tablet:items-center flex shrink-0 flex-col items-stretch gap-200">
<Button variant="secondary" size="md" onClick={onEdit}>
수정
</Button>
Expand Down
17 changes: 11 additions & 6 deletions src/components/admin/schedule/general/SchedulePageContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ function SchedulePageContent() {
};

return (
<div className="flex min-w-0 flex-col gap-400 p-700">
<div className="tablet:p-700 flex min-w-0 flex-col gap-400 p-400">
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: 프로젝트의 Tailwind 설정 및 CSS에서 p-700, gap-700 등 확장된 spacing 토큰 정의 확인

# Tailwind config 파일 확인
fd -e js -e ts 'tailwind.config' --exec cat {}

# CSS 파일에서 `@theme` 지시어나 spacing 토큰 정의 확인
rg -n '`@theme`|--spacing-700|p-700' --type=css -C3

# globals.css나 메인 스타일 파일 확인
fd 'globals.css' --exec cat {}

Repository: Team-Weeth/weeth-client

Length of output: 23988


spacing 토큰 범위(가이드) 위반 여부 확인 필요

src/app/globals.css@theme inline--spacing-700: 32px가 정의되어 있어 tablet:p-700은 유효한 클래스입니다. 다만 코딩 가이드의 padding 토큰 범위(p-100~500)를 벗어나므로 tablet:p-700 사용 의도가 맞는지 확인하고(32px 필요 여부) 가능하면 p-500 이하 토큰으로 조정이 필요합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/admin/schedule/general/SchedulePageContent.tsx` at line 102,
The div in SchedulePageContent.tsx uses the class token "tablet:p-700" which is
outside the project's padding token guideline (allowed p-100..p-500); confirm
whether the 32px padding from --spacing-700 is intentionally required for this
element, and if not replace "tablet:p-700" with an appropriate in-range token
such as "tablet:p-500" (or another p-100..p-500 value) in the className string;
if the 700 value is intentional, add an inline comment near the div or a short
justification in the PR explaining why the exception is needed.

<CardinalDropdown
cardinals={cardinals}
activeCardinal={activeCardinal}
Expand All @@ -114,8 +114,8 @@ function SchedulePageContent() {
<TabsTrigger value="session">세션</TabsTrigger>
</TabsList>

<TabsContent value="all" className="mt-400 overflow-x-auto">
<Card className="min-w-172.5 gap-700 px-600 pt-600 pb-800">
<TabsContent value="all" className="mt-400">
<Card className="tablet:gap-700 tablet:px-600 tablet:pt-600 tablet:pb-800 min-w-78 gap-600 px-400 pt-400 pb-600">
{/* Month navigator */}
<MonthNavigator
year={currentYear}
Expand All @@ -125,8 +125,8 @@ function SchedulePageContent() {
/>

{/* Search bar + Create button */}
<div className="flex items-center justify-between">
<div className="relative w-123">
<div className="tablet:flex-row tablet:flex-wrap tablet:items-center tablet:justify-between flex flex-col gap-300">
<div className="tablet:w-123 relative w-full">
<Image
src={SearchIcon}
alt="검색"
Expand All @@ -144,7 +144,12 @@ function SchedulePageContent() {
className="bg-container-neutral-alternative typo-body1 placeholder:text-text-alternative h-12 w-full rounded-sm py-300 pr-300 pl-14 focus:outline-none"
/>
</div>
<Button variant="primary" size="lg" onClick={() => openCreateModal('EVENT')}>
<Button
variant="primary"
size="lg"
className="tablet:w-auto tablet:shrink-0 w-full whitespace-nowrap"
onClick={() => openCreateModal('EVENT')}
>
<Icon src={AdminCalendarEditIcon} size={20} className="text-text-inverse mr-1" />
일반 일정 생성
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
isScheduleTitleValid,
} from '@/utils/admin/scheduleFormUtils';

import { SCHEDULE_MODAL_FOOTER_CLASS } from './constants';
import { isDateRangeValid, type ScheduleFormState } from './types';

const INITIAL_FORM: ScheduleFormState = {
Expand Down Expand Up @@ -60,7 +61,7 @@ function CreateGeneralScheduleForm({ cardinalNumber, onClose }: CreateGeneralSch

return (
<>
<div className="scrollbar-custom tablet:px-15 max-h-175 overflow-y-auto px-400">
<div className="scrollbar-custom tablet:px-15 max-h-175 min-h-0 overflow-y-auto px-400">
<h2 className="typo-h3 text-text-normal py-400">일정 생성</h2>
<ScheduleFormBody
form={form}
Expand All @@ -70,7 +71,7 @@ function CreateGeneralScheduleForm({ cardinalNumber, onClose }: CreateGeneralSch
/>
</div>

<div className="bg-container-neutral flex items-center justify-end gap-200 px-400 pt-400 pb-500">
<div className={SCHEDULE_MODAL_FOOTER_CLASS}>
<Button variant="secondary" size="lg" onClick={onClose}>
취소
</Button>
Expand Down
4 changes: 3 additions & 1 deletion src/components/admin/schedule/modal/CreateScheduleModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Dialog, DialogContent } from '@/components/ui/dialog';
import { AdminCloseIcon } from '@/assets/icons/admin';
import { CreateGeneralScheduleForm } from '@/components/admin/schedule/modal/CreateGeneralScheduleForm';
import { CreateSessionScheduleForm } from '@/components/admin/schedule/modal/CreateSessionScheduleForm';
import { SCHEDULE_MODAL_CONTENT_CLASS } from '@/components/admin/schedule/modal/constants';
import { SCHEDULE_TYPE_LABEL } from '@/constants/admin/schedule.constants';
import type { ScheduleType } from '@/types/admin/schedule';
import type { CreateSessionBody } from '@/types/admin/session';
Expand All @@ -31,8 +32,9 @@ function CreateScheduleModal({
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent
className="bg-background flex w-215 max-w-[calc(100%-2rem)] flex-col gap-0 overflow-hidden rounded-lg p-0"
className={SCHEDULE_MODAL_CONTENT_CLASS}
showCloseButton={false}
adminMobileFullscreen={false}
>
{/* Header with tabs */}
<div className="tablet:px-700 tablet:pt-700 flex items-start justify-between px-400 pt-400">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type { CreateSessionBody } from '@/types/admin/session';

import { isScheduleTitleValid } from '@/utils/admin/scheduleFormUtils';

import { SCHEDULE_MODAL_FOOTER_CLASS } from './constants';
import { isDateRangeValid, type ScheduleFormState, type SessionFormState } from './types';

const INITIAL_FORM: ScheduleFormState = {
Expand Down Expand Up @@ -104,7 +105,7 @@ function CreateSessionScheduleForm({ onCreateSession, onClose }: CreateSessionSc

return (
<>
<div className="scrollbar-custom tablet:px-15 max-h-175 overflow-y-auto px-400">
<div className="scrollbar-custom tablet:px-15 max-h-175 min-h-0 overflow-y-auto px-400">
<h2 className="typo-h3 text-text-normal py-400">세션 생성</h2>
<SessionScheduleForm
form={form}
Expand All @@ -117,7 +118,7 @@ function CreateSessionScheduleForm({ onCreateSession, onClose }: CreateSessionSc
/>
</div>

<div className="bg-container-neutral flex items-center justify-end gap-200 px-400 pt-400 pb-500">
<div className={SCHEDULE_MODAL_FOOTER_CLASS}>
<Button variant="secondary" size="lg" onClick={onClose}>
취소
</Button>
Expand Down
4 changes: 3 additions & 1 deletion src/components/admin/schedule/modal/EditModalShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Suspense } from 'react';
import type { ReactNode, RefObject } from 'react';

import { Dialog, DialogContent } from '@/components/ui/dialog';
import { SCHEDULE_MODAL_CONTENT_CLASS } from '@/components/admin/schedule/modal/constants';

interface EditModalShellProps {
open: boolean;
Expand Down Expand Up @@ -44,8 +45,9 @@ function EditModalShell({
}}
>
<DialogContent
className="bg-background flex w-215 max-w-[calc(100%-2rem)] flex-col gap-0 overflow-hidden rounded-lg p-0"
className={SCHEDULE_MODAL_CONTENT_CLASS}
showCloseButton={false}
adminMobileFullscreen={false}
onPointerDownOutside={(e) => {
if (hasChangesRef.current) e.preventDefault();
}}
Expand Down
5 changes: 3 additions & 2 deletions src/components/admin/schedule/modal/EditScheduleModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
toInitialScheduleForm,
} from '@/utils/admin/scheduleFormUtils';

import { SCHEDULE_MODAL_FOOTER_CLASS } from './constants';
import { DiscardConfirmArea } from './DiscardConfirmArea';
import { EditModalShell } from './EditModalShell';
import { ScheduleFormBody } from './ScheduleFormBody';
Expand Down Expand Up @@ -207,7 +208,7 @@ function EditScheduleModalContent({
</div>

{/* Body */}
<div className="scrollbar-custom tablet:px-700 max-h-175 overflow-y-auto px-400">
<div className="scrollbar-custom tablet:px-700 max-h-175 min-h-0 overflow-y-auto px-400">
<h2 className="typo-h3 text-text-normal py-400">일반 일정 수정</h2>
<ScheduleFormBody
form={form}
Expand All @@ -218,7 +219,7 @@ function EditScheduleModalContent({
</div>

{/* Footer */}
<div className="bg-container-neutral flex items-center justify-end gap-200 px-400 pt-400 pb-500">
<div className={SCHEDULE_MODAL_FOOTER_CLASS}>
<DiscardConfirmArea
open={discardSource === 'cancel'}
onOpenChange={closeDiscardAlert('cancel')}
Expand Down
5 changes: 5 additions & 0 deletions src/components/admin/schedule/modal/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const SCHEDULE_MODAL_CONTENT_CLASS =
'bg-background flex max-h-[calc(100%-2rem)] w-215 max-w-[calc(100%-2rem)] min-w-90 flex-col gap-0 overflow-hidden rounded-lg p-0';
Comment on lines +1 to +2
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

토큰 우선 규칙에 맞게 임의값(calc) 클래스 정리가 필요합니다.

max-h-[calc(100%-2rem)], max-w-[calc(100%-2rem)]는 하드코딩된 임의값입니다. 이 파일은 공용 상수화 지점이므로 토큰 클래스 우선으로 정리하거나, 토큰이 없다면 먼저 토큰 추가 합의를 거치는 편이 안전합니다.
As per coding guidelines: "Always use design token classes first; no hardcoded values. Ask user before adding new tokens".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/admin/schedule/modal/constants.ts` around lines 1 - 2,
SCHEDULE_MODAL_CONTENT_CLASS uses hardcoded calc classes
(max-h-[calc(100%-2rem)], max-w-[calc(100%-2rem)]) which violates the
token-first rule; update the constant to use existing design token classes for
max-height and max-width (replace those calc classes with the appropriate token
class names) and if no suitable tokens exist, do not hardcode new values—raise a
proposal/PR to add modal size tokens and then reference those new token classes
in SCHEDULE_MODAL_CONTENT_CLASS.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

소형 화면에서 모달 가로 오버플로가 발생할 수 있습니다.

min-w-90max-w-[calc(100%-2rem)]보다 커지는 뷰포트(예: 360px/375px)에서는 최소 너비가 우선되어 모달이 화면을 벗어납니다. adminMobileFullscreen={false}를 쓰는 스케줄 모달 전반에 영향이 있습니다.

🔧 제안 수정안
 export const SCHEDULE_MODAL_CONTENT_CLASS =
-  'bg-background flex max-h-[calc(100%-2rem)] w-215 max-w-[calc(100%-2rem)] min-w-90 flex-col gap-0 overflow-hidden rounded-lg p-0';
+  'bg-background flex max-h-[calc(100%-2rem)] w-215 max-w-[calc(100%-2rem)] tablet:min-w-90 flex-col gap-0 overflow-hidden rounded-lg p-0';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const SCHEDULE_MODAL_CONTENT_CLASS =
'bg-background flex max-h-[calc(100%-2rem)] w-215 max-w-[calc(100%-2rem)] min-w-90 flex-col gap-0 overflow-hidden rounded-lg p-0';
export const SCHEDULE_MODAL_CONTENT_CLASS =
'bg-background flex max-h-[calc(100%-2rem)] w-215 max-w-[calc(100%-2rem)] tablet:min-w-90 flex-col gap-0 overflow-hidden rounded-lg p-0';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/admin/schedule/modal/constants.ts` around lines 1 - 2, The
modal can overflow on small viewports because SCHEDULE_MODAL_CONTENT_CLASS
includes min-w-90 which can be larger than max-w-[calc(100%-2rem)]; update the
class string used by SCHEDULE_MODAL_CONTENT_CLASS to remove or replace min-w-90
with a non-blocking value (e.g., min-w-0 or remove the min-w utility) so the
max-w-[calc(100%-2rem)] can constrain the modal on narrow screens (this affects
the schedule modal when adminMobileFullscreen={false}).


export const SCHEDULE_MODAL_FOOTER_CLASS =
'bg-container-neutral flex shrink-0 items-center justify-end gap-200 px-400 pt-400 pb-500';
4 changes: 2 additions & 2 deletions src/components/admin/schedule/session/SessionTabContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ function SessionTabContent({
{/* 세션 카드 */}
<div className="bg-container-neutral flex flex-col rounded-lg shadow-sm">
{/* 카드 헤더 */}
<div className="flex h-[72px] items-center justify-between px-600">
<div className="tablet:px-600 flex h-[72px] items-center justify-between px-400">
<span className="typo-sub3 text-text-normal">세션</span>
<Button variant="primary" size="lg" onClick={onCreateSession}>
<Image src={AdminCalendarEditIcon} alt="" width={20} height={20} className="mr-1" />
Expand All @@ -74,7 +74,7 @@ function SessionTabContent({
</div>

{/* 카드 body */}
<div className="p-600 pt-0">
<div className="tablet:p-600 tablet:pt-0 p-400 pt-0">
<SessionTable
groups={sessions}
onManageAttendance={onManageAttendance}
Expand Down
10 changes: 4 additions & 6 deletions src/components/ui/dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { cn } from '@/lib/cn';
import { Button } from '@/components/ui/Button';
import { Divider } from '@/components/ui/Divider';
import { Icon } from '@/components/ui/Icon';
// import { AdminMobileBlockedContent } from '@/components/admin/AdminMobileBlockedContent';
import { AdminScopeBoundary, useIsAdminScope } from '@/providers';

function Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) {
Expand Down Expand Up @@ -52,10 +51,13 @@ function DialogContent({
overlayClassName,
children,
showCloseButton = true,
adminMobileFullscreen = true,
...props
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
showCloseButton?: boolean;
overlayClassName?: string;
/** admin 스코프에서 tablet 미만일 때 전체화면으로 전환할지 여부 (기본 true) */
adminMobileFullscreen?: boolean;
}) {
const isAdminScope = useIsAdminScope();

Expand All @@ -68,17 +70,13 @@ function DialogContent({
className={cn(
'bg-container-neutral 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-[80] grid w-78.75 max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] rounded-lg border p-400 shadow-lg duration-200 outline-none sm:max-w-lg',
isAdminScope &&
adminMobileFullscreen &&
'max-tablet:inset-0 max-tablet:top-0 max-tablet:left-0 max-tablet:h-screen max-tablet:w-screen max-tablet:max-w-none max-tablet:translate-x-0 max-tablet:translate-y-0 max-tablet:rounded-none max-tablet:border-0 max-tablet:p-0',
className,
)}
{...props}
>
{children}
{/* {isAdminScope ? (
<AdminMobileBlockedContent>{children}</AdminMobileBlockedContent>
) : (
children
)} */}
{showCloseButton && (
<DialogPrimitive.Close
data-slot="dialog-close"
Expand Down
1 change: 1 addition & 0 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ export { useImageDrop } from './useImageDrop';
export { useProgressAnimation } from './useProgressAnimation';
export { useCodeHighlight } from './useCodeHighlight';
export { useMonthNavigator } from './useMonthNavigator';
export { useMediaQuery } from './useMediaQuery';
22 changes: 22 additions & 0 deletions src/hooks/useMediaQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use client';

import { useSyncExternalStore } from 'react';

/**
* CSS 미디어 쿼리의 매치 여부를 구독한다.
* SSR 시에는 항상 false를 반환하고, 마운트 후 실제 값으로 동기화되어 하이드레이션 불일치를 피한다.
*/
function useMediaQuery(query: string): boolean {
const subscribe = (callback: () => void) => {
const mql = window.matchMedia(query);
mql.addEventListener('change', callback);
return () => mql.removeEventListener('change', callback);
};

const getSnapshot = () => window.matchMedia(query).matches;
const getServerSnapshot = () => false;

return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
}

export { useMediaQuery };
Loading