diff --git a/.cursor/rules/web-fsd-architecture.mdc b/.cursor/rules/web-fsd-architecture.mdc new file mode 100644 index 00000000..5ae120ee --- /dev/null +++ b/.cursor/rules/web-fsd-architecture.mdc @@ -0,0 +1,107 @@ +--- +description: apps/web 프로젝트 코드 작성 시 FSD-inspired 하이브리드 아키텍처 규칙을 적용합니다. +globs: + - apps/web/src/**/*.ts + - apps/web/src/**/*.tsx +alwaysApply: false +--- + +# apps/web FSD 아키텍처 규칙 + +## 레이어 구조와 의존성 + +apps/web은 FSD-inspired 하이브리드 아키텍처를 사용합니다. + +``` +src/ +├── app/ # 라우팅 + 조합 (Next.js App Router) +├── widgets/ # 독립적 대규모 UI 블록 +├── features/ # 사용자 액션 기능 +├── entities/ # 비즈니스 엔티티 +├── shared/ # 범용 기반 코드 +├── components/ # [레거시] 새 파일 추가 금지 +└── middleware.ts +``` + +의존성 규칙: `app/ → widgets/ → features/ → entities/ → shared/` + +## 핵심 규칙 + +1. **상위 레이어는 하위 레이어만 import 가능** + - features/는 entities/와 shared/만 import + - entities/는 shared/만 import + - shared/는 외부 패키지만 import + +2. **같은 레이어 슬라이스 간 import 금지** + - entities/persona/에서 entities/guild/ import 불가 + - features/auction/에서 features/feedback/ import 불가 + +3. **components/ 디렉토리에 새 파일 추가 금지** + - 새 코드는 반드시 적절한 FSD 레이어에 배치 + - 도메인 무관 UI → shared/ui/ + - 도메인 관련 UI → entities/*/ui/ + - 기능 UI → features/*/ui/ + - 대규모 조합 블록 → widgets/ + +4. **각 슬라이스는 barrel index.ts 제공** + ```typescript + // entities/persona/index.ts + export * from './ui'; + export * from './model'; + ``` + +## 새 코드 배치 기준 + +| 코드 유형 | 배치 위치 | 예시 | +|---|---|---| +| 라우트 전용 UI | `app/[locale]/*/_components/` | 페이지별 폼, 섹션 | +| 범용 UI (도메인 무관) | `shared/ui/` | Modal, Button | +| 엔티티 UI | `entities/*/ui/` | PersonaItem, GuildCard | +| 엔티티 쿼리/로직 | `entities/*/model/` | useGetAllPersona | +| 사용자 액션 | `features/*/model/` | useRegisterProduct | +| API 호출 함수 | `features/*/api/` 또는 `shared/api/` | github.ts | +| 독립 대규모 블록 | `widgets/*/` | GNB | +| 유틸리티 | `shared/utils/` | image.ts, string.ts | +| 상수/설정 | `shared/config/` | env.ts, route.ts | +| hooks (범용) | `shared/hooks/` | usePagination | + +## 슬라이스 내부 구조 + +``` +features/auction/ +├── api/ # API 호출 함수 +├── model/ # hooks, 상태, 비즈니스 로직 +├── ui/ # UI 컴포넌트 +└── index.ts # public API (barrel export) +``` + +## import 스타일 + +```typescript +// 외부 패키지 +import { useQuery } from '@tanstack/react-query'; +import { getAllPersona } from '@gitanimals/api'; + +// FSD 레이어 (절대경로 @/) +import GNB from '@/widgets/gnb/GNB'; +import { AnimalCard } from '@/entities/persona'; +import { getPersonaImage } from '@/shared/utils/image'; + +// 같은 슬라이스 내 (상대경로) +import { useGetAllPersona } from './useGetAllPersona'; +``` + +## `@gitanimals/*` 패키지 + +모노레포 패키지는 외부 shared 계층으로 취급합니다. +entities, features에서 직접 import 허용: + +```typescript +import { getAllPersona } from '@gitanimals/api'; // OK +import { cn } from '@gitanimals/ui-tailwind'; // OK +import { userQueries } from '@gitanimals/react-query'; // OK +``` + +## 상세 가이드 + +전체 아키텍처 가이드는 `apps/web/ARCHITECTURE.md`를 참조하세요. diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml new file mode 100644 index 00000000..a535130c --- /dev/null +++ b/.github/workflows/chromatic.yml @@ -0,0 +1,44 @@ +name: Chromatic - UI Storybook + +on: + push: + branches: + - dev + paths: + - 'packages/ui/tailwind/**' + +env: + DEFAULT_NODE_VERSION: '20.11.0' + PNPM_VERSION: '9.0.6' + +jobs: + chromatic: + name: Publish Storybook + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: ${{ env.PNPM_VERSION }} + run_install: false + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: ${{ env.DEFAULT_NODE_VERSION }} + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install + + - name: Run Chromatic + uses: chromaui/action@latest + with: + projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} + workingDir: packages/ui/tailwind + exitOnceUploaded: true diff --git a/CLAUDE.md b/CLAUDE.md index f88c068d..3ac4f02d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,13 +21,32 @@ GitAnimals is a monorepo that allows users to raise virtual pets through GitHub - `packages/util/*` - Utility packages (common, typescript) **Key Technologies:** -- **Styling:** PandaCSS with Shadow Panda preset for component styling -- **State Management:** Tanstack Query v5 (server state), Jotai & Zustand (client state) +- **Styling:** Tailwind CSS (migrating from PandaCSS) +- **State Management:** Tanstack Query v5 (server state), Jotai (client state) - **Authentication:** NextAuth.js (web), token-based auth (webview) -- **UI Components:** Radix UI primitives + custom PandaCSS components +- **UI Components:** Radix UI primitives + `@gitanimals/ui-tailwind` - **Package Manager:** pnpm v9.0+ with workspace configuration - **Build System:** Turborepo for orchestration +**Web App Architecture (FSD-inspired Hybrid):** + +apps/web은 FSD(Feature-Sliced Design) 기반 하이브리드 아키텍처를 사용합니다. +상세 가이드: [`apps/web/ARCHITECTURE.md`](apps/web/ARCHITECTURE.md) + +``` +src/ +├── app/ # Next.js App Router (라우팅 + 조합, FSD app+pages 역할) +├── widgets/ # 독립적 대규모 UI 블록 (GNB 등) +├── features/ # 사용자 액션 기능 (auction, auth, feedback 등) +├── entities/ # 비즈니스 엔티티 (persona, guild, user, product) +├── shared/ # 범용 기반 코드 (api, config, hooks, i18n, lib, store, utils) +├── components/ # [레거시] 점진적으로 shared/ui/ 또는 entities/*/ui/로 이전 +└── middleware.ts +``` + +의존성 규칙: `app/ → widgets/ → features/ → entities/ → shared/` +상위 레이어는 하위 레이어만 import 가능. 같은 레이어 슬라이스 간 import 금지. + ## Development Commands **Root-level commands (run from project root):** @@ -68,20 +87,23 @@ pnpm --filter @gitanimals/ui-panda storybook # Start Storybook ## Code Patterns & Conventions -**Component Architecture:** -- UI components in `packages/ui/panda/src/components/` -- Each component has: `Component.tsx`, `Component.stories.tsx`, `index.ts` -- PandaCSS styling with design tokens from `packages/ui/token` +**Component Architecture (apps/web):** +- FSD 레이어 구조: `shared/ → entities/ → features/ → widgets/ → app/` +- 각 슬라이스는 `ui/`, `model/`, `api/` 세그먼트와 barrel `index.ts`로 구성 +- 라우트 전용 UI는 `app/[locale]/*/_components/`에 코로케이션 +- 도메인 무관 공유 UI는 `shared/ui/` 또는 `components/`(레거시) **Import Patterns:** - Workspace dependencies use `workspace:*` protocol -- UI components imported from `@gitanimals/ui-panda` +- UI components imported from `@gitanimals/ui-tailwind` - Shared utilities from `@gitanimals/util-common` +- Web app 내부는 `@/` alias 사용 (→ `src/`) +- FSD 의존성 규칙 준수: `@/shared/`, `@/entities/`, `@/features/`, `@/widgets/` **State Management:** -- Server state: Tanstack Query v5 with `queryOptions` pattern in `src/apis/` -- Client state: Jotai for atomic state, Zustand for stores -- Auth state managed through NextAuth.js +- Server state: Tanstack Query v5 in `entities/*/model/` 및 `features/*/model/` +- Client state: Jotai atoms in `shared/store/` +- Auth state managed through NextAuth.js (`shared/api/auth.ts`) **Tanstack Query v5 Best Practices:** - Always use `queryOptions` factory pattern for reusable query definitions @@ -120,7 +142,7 @@ pnpm --filter @gitanimals/ui-panda storybook # Start Storybook // Usage in component import { useQuery } from '@tanstack/react-query'; - import { userQueryOptions } from '@/apis/user/queries'; + import { userQueryOptions } from '@/entities/user/model/queries'; function UserProfile({ userId }: { userId: string }) { const { data: user } = useQuery(userQueryOptions.getUser(userId)); @@ -139,10 +161,10 @@ pnpm --filter @gitanimals/ui-panda storybook # Start Storybook - No need for custom hooks unless adding extra logic **Styling Approach:** -- PandaCSS with `styled-system` generation -- Shadow Panda preset for enhanced component styling -- Responsive design using PandaCSS conditions (mobile/desktop) -- Design tokens centralized in `packages/ui/token` +- Tailwind CSS (migrating from PandaCSS) +- `@gitanimals/ui-tailwind` 디자인 시스템 컴포넌트 +- Responsive design using Tailwind breakpoints +- `cn()` utility for conditional class merging ## Testing & Quality @@ -155,7 +177,7 @@ pnpm --filter @gitanimals/ui-panda storybook # Start Storybook - `turbo.json` - Build pipeline configuration - `pnpm-workspace.yaml` - Workspace definition -- `apps/*/panda.config.ts` - PandaCSS configuration per app +- `apps/web/ARCHITECTURE.md` - **FSD 아키텍처 상세 가이드 (필독)** - `packages/ui/panda/src/theme/` - Design system tokens and styles ## Build Pipeline Dependencies @@ -167,7 +189,9 @@ The build system has specific dependency chains: ## Notes for Development -- Always run `panda codegen` after theme changes - UI component changes require Storybook restart - Mobile app uses Expo SDK ~53.0.17 with React Native 0.79.5 -- Web app uses Next.js App Router with internationalization (next-intl) \ No newline at end of file +- Web app uses Next.js App Router with internationalization (next-intl) +- **apps/web 작업 시 반드시 FSD 의존성 규칙을 따를 것** (상세: `apps/web/ARCHITECTURE.md`) +- 새 코드는 반드시 `shared/`, `entities/`, `features/`, `widgets/` 중 적절한 레이어에 배치 +- `components/`(레거시)에 새 파일 추가 금지 — 적절한 FSD 레이어 사용 \ No newline at end of file diff --git a/apps/web/ARCHITECTURE.md b/apps/web/ARCHITECTURE.md new file mode 100644 index 00000000..24774b7e --- /dev/null +++ b/apps/web/ARCHITECTURE.md @@ -0,0 +1,320 @@ +# apps/web 아키텍처 가이드 + +## 개요 + +apps/web은 **FSD-inspired 하이브리드 아키텍처**를 사용합니다. +Next.js App Router의 파일 기반 라우팅 + 코로케이션을 유지하면서, FSD(Feature-Sliced Design)의 레이어 원칙과 의존성 규칙을 적용합니다. + +## 디렉토리 구조 + +``` +src/ +├── app/ # 라우팅 + 페이지 조합 (FSD app+pages 역할) +│ ├── [locale]/ # 국제화 라우트 +│ │ ├── page.tsx # widgets/features/entities를 조합하여 페이지 구성 +│ │ ├── layout.tsx # locale shell (providers, i18n) +│ │ ├── mypage/ # 마이페이지 (라우트 그룹, _components 코로케이션) +│ │ ├── shop/ # 상점 (_auction, _background, _petGotcha) +│ │ ├── guild/ # 길드 (@modal 병렬 라우트, (subpage) 그룹) +│ │ ├── game/ # 게임/퀴즈 +│ │ ├── laboratory/ # 실험실 +│ │ ├── event/ # 이벤트 (halloween, christmas) +│ │ └── landing/ # 랜딩 섹션 컴포넌트 (라우트 아님) +│ ├── api/ # Route Handlers (auth, oauth) +│ └── (noLocale)/ # locale 없는 라우트 +│ +├── widgets/ # 독립적 대규모 UI 블록 +│ └── gnb/ # GNB (DesktopGNB, MobileGNB, Notification) +│ +├── features/ # 사용자 액션 단위 기능 +│ ├── auction/ # 경매 등록 (useRegisterProduct) +│ ├── auth/ # GitHub OAuth 인증 +│ ├── change-persona-visible/ # 펫 표시/숨김 전환 +│ ├── feedback/ # GitHub 이슈/피드백 보고 +│ └── laboratory-feedback/ # 실험실 피드백 (Supabase 기반) +│ +├── entities/ # 비즈니스 엔티티 +│ ├── persona/ # 펫 +│ │ ├── ui/ # AnimalCard, PersonaItem, PersonaListToolbar +│ │ └── model/ # useGetAllPersona, usePersonaListFilter, useGetPersonaDropRate +│ ├── guild/ # 길드 +│ │ └── ui/ # GuildMemberSlider +│ ├── user/ # 사용자 +│ │ └── model/ # useGetTotalRenderUserCount, useGetTotalIdentityUserCount +│ └── product/ # 상품 +│ └── model/ # useGetTotalProductCount +│ +├── shared/ # 범용 기반 코드 (모든 레이어에서 import 가능) +│ ├── api/ # HTTP client, interceptor, auth(NextAuth), QueryClientProvider +│ ├── config/ # 상수, 환경변수, 라우트, animalTier +│ ├── hooks/ # usePagination, useOutsideClick, useDeviceInfo 등 +│ ├── i18n/ # next-intl 설정 (routing, request, useToggleLocale) +│ ├── lib/ # QueryClient, analytics, Supabase, devtools +│ ├── store/ # Jotai atoms (snackBar, loading) +│ ├── styles/ # 스타일 헬퍼 +│ ├── ui/ # Gitanimals 렌더링 컴포넌트 +│ ├── utils/ # 유틸리티 (animals, image, string, number 등) +│ ├── assets/ # 정적 자산 (Lottie 등) +│ ├── exceptions/ # 에러 타입 +│ └── schema/ # 유효성 검증 스키마 +│ +├── components/ # [레거시] 아직 분류되지 않은 공유 UI +│ # → 점진적으로 shared/ui/ 또는 entities/*/ui/로 이전 +│ +├── serverActions/ # Server Actions +│ # → 점진적으로 features/*/api/로 이전 +│ +└── middleware.ts # next-intl + next-auth 미들웨어 +``` + +## 의존성 규칙 + +**핵심 원칙: 상위 레이어는 하위 레이어만 import할 수 있다.** + +``` +app/ → widgets/ → features/ → entities/ → shared/ +``` + +### 허용되는 import 방향 + +| From (소스) | To (대상) | 허용 | +|---|---|---| +| `app/` | `widgets/`, `features/`, `entities/`, `shared/`, `components/` | O | +| `widgets/` | `features/`, `entities/`, `shared/`, `components/` | O | +| `features/` | `entities/`, `shared/` | O | +| `entities/` | `shared/` | O | +| `shared/` | (없음, 외부 패키지만) | O | + +### 금지되는 import + +| From (소스) | To (대상) | 금지 이유 | +|---|---|---| +| `shared/` | `entities/`, `features/`, `widgets/` | 하위 → 상위 금지 | +| `entities/` | `features/`, `widgets/` | 하위 → 상위 금지 | +| `features/` | `widgets/` | 하위 → 상위 금지 | +| `entities/A` | `entities/B` | 같은 레이어 슬라이스 간 import 금지 | +| `features/A` | `features/B` | 같은 레이어 슬라이스 간 import 금지 | + +### 예외: 모노레포 패키지 + +`@gitanimals/*` 패키지 (`@gitanimals/api`, `@gitanimals/react-query`, `@gitanimals/ui-tailwind` 등)는 **외부 shared 계층**으로 취급합니다. entities, features에서 직접 import 허용: + +```typescript +// entities/persona/model/useGetAllPersona.ts — 허용 +import { getAllPersona } from '@gitanimals/api'; +``` + +## 레이어별 상세 가이드 + +### app/ (라우팅 + 조합) + +- Next.js App Router의 `page.tsx`, `layout.tsx`, `loading.tsx`, `error.tsx` 컨벤션을 그대로 사용 +- 페이지는 **widgets, features, entities를 조합**하는 역할 +- 라우트 전용 UI는 `_components/` 폴더에 코로케이션 (App Router 컨벤션) +- `(route group)`, `@parallel` 라우트 등 Next.js 고급 패턴 활용 + +```typescript +// app/[locale]/mypage/page.tsx — 조합 역할 +import GNB from '@/widgets/gnb/GNB'; +import { PersonaList } from './PersonaList'; // 라우트 전용 (코로케이션) +import { MemoizedBannerPersonaItem } from '@/entities/persona'; +``` + +**`_components/` 승격 규칙:** 라우트 전용 `_components/` 내 코드가 2개 이상의 라우트에서 재사용되면, `widgets/` 또는 `entities/*/ui/`로 승격합니다. + +### widgets/ (독립 UI 블록) + +- 여러 entities/features를 조합한 **독립적 대규모 UI 블록** +- 자체적인 비즈니스 로직을 포함할 수 있음 +- 각 widget은 barrel `index.ts`를 통해 public API 노출 + +``` +widgets/ +└── gnb/ + ├── GNB.tsx # 진입점 (Desktop/Mobile 분기) + ├── DesktopGNB.tsx + ├── MobileGNB.tsx + ├── LanguageSelector.tsx + ├── Notification/ + └── index.ts # export { default as GNB } from './GNB'; +``` + +### features/ (사용자 액션) + +- **사용자에게 비즈니스 가치를 제공하는 기능 단위** +- 각 feature는 `api/`, `model/`, `ui/` 세그먼트로 구성 +- barrel `index.ts`를 통해 public API만 노출 + +``` +features/ +└── auction/ + ├── model/ + │ └── useRegisterProduct.ts # useMutation hook + └── index.ts # public API +``` + +**새 feature 생성 시:** +```bash +mkdir -p src/features/{name}/{api,model,ui} +touch src/features/{name}/index.ts +``` + +### entities/ (비즈니스 엔티티) + +- 프로젝트가 다루는 **비즈니스 도메인 엔티티** +- `ui/`: 엔티티 관련 UI 컴포넌트 (AnimalCard, PersonaItem 등) +- `model/`: 엔티티 관련 hooks, types, 비즈니스 로직 +- barrel `index.ts`를 통해 public API 노출 + +``` +entities/ +└── persona/ + ├── ui/ + │ ├── AnimalCard.tsx + │ ├── PersonaItem.tsx + │ ├── PersonaListToolbar.tsx + │ └── index.ts + ├── model/ + │ ├── useGetAllPersona.ts + │ ├── usePersonaListFilter.ts + │ ├── useGetPersonaDropRate.ts + │ └── index.ts + └── index.ts # re-export ui + model +``` + +**entities 간 import 금지:** `entities/persona/`에서 `entities/guild/`를 import할 수 없습니다. 두 엔티티를 조합해야 하면 상위 레이어(features 또는 widgets)에서 수행합니다. + +### shared/ (범용 기반) + +- **모든 레이어에서 재사용**되는 코드 +- 비즈니스 로직을 포함하지 않는 순수 유틸리티/인프라 + +| 세그먼트 | 역할 | 예시 | +|---|---|---| +| `api/` | HTTP 클라이언트, 인터셉터, 인증 | auth.ts, interceptor.ts, QueryClientProvider | +| `config/` | 상수, 환경변수, 설정 | env.ts, route.ts, animalTier.ts | +| `hooks/` | 범용 React hooks | usePagination, useOutsideClick | +| `i18n/` | 국제화 설정 | routing.ts, request.ts | +| `lib/` | 외부 라이브러리 설정 | queryClient.ts, analytics.ts, supabase/ | +| `store/` | 전역 클라이언트 상태 | snackBar.ts (Jotai), loading.ts | +| `styles/` | 스타일 헬퍼/토큰 | scrollStyle.ts | +| `ui/` | 범용 UI 컴포넌트 | Gitanimals.tsx | +| `utils/` | 유틸리티 함수 | animals.ts, image.ts, string.ts | + +## import 규칙 + +### 경로 alias + +```json +// tsconfig.json +{ "paths": { "@/*": ["./src/*"] } } +``` + +### import 스타일 + +```typescript +// 1. 외부 패키지 +import { useQuery } from '@tanstack/react-query'; +import { getAllPersona } from '@gitanimals/api'; + +// 2. FSD 레이어 (절대경로 @/ 사용) +import GNB from '@/widgets/gnb/GNB'; +import { useRegisterProduct } from '@/features/auction/model/useRegisterProduct'; +import { AnimalCard } from '@/entities/persona'; +import { getPersonaImage } from '@/shared/utils/image'; + +// 3. 같은 슬라이스 내부 (상대경로 허용) +import { useGetAllPersona } from './useGetAllPersona'; // 같은 model/ 내 +``` + +### barrel export (index.ts) + +각 슬라이스는 `index.ts`를 통해 public API를 노출합니다: + +```typescript +// entities/persona/index.ts +export * from './ui'; +export * from './model'; +``` + +**외부에서는 barrel을 통해 import하는 것을 권장합니다:** + +```typescript +// 권장: barrel import +import { AnimalCard, usePersonaListFilter } from '@/entities/persona'; + +// 허용: 직접 import (barrel이 너무 많은 것을 노출할 때) +import { usePersonaListFilter } from '@/entities/persona/model/usePersonaListFilter'; +``` + +## 새 코드 작성 가이드 + +### 새 페이지 추가 + +1. `app/[locale]/{route}/page.tsx` 생성 +2. 페이지 전용 UI는 `_components/`에 코로케이션 +3. 재사용 가능한 로직은 적절한 레이어로 분리 + +### 새 feature 추가 + +1. `features/{name}/` 디렉토리 생성 +2. `api/` (API 호출), `model/` (hooks, 로직), `ui/` (UI) 세그먼트 배치 +3. `index.ts` barrel export 작성 +4. 의존성 규칙 확인: entities와 shared만 import + +### 새 entity 추가 + +1. `entities/{name}/` 디렉토리 생성 +2. `ui/` (표현 컴포넌트), `model/` (hooks, types) 세그먼트 배치 +3. `index.ts` barrel export 작성 +4. 의존성 규칙 확인: shared만 import, 다른 entity import 금지 + +### `components/`에서의 점진적 이전 + +`components/`에 남아있는 코드는 레거시입니다. 수정 시 적절한 레이어로 이전하세요: + +| 현재 위치 | 이전 대상 | 조건 | +|---|---|---| +| `components/Select/`, `Tabs/`, `Pagination/` | `shared/ui/` | 도메인 무관 범용 UI | +| `components/Global/` | `shared/ui/` + `app/` providers | 전역 프로바이더/셸 | +| `components/Error/` | `shared/ui/` | 에러 표시 UI | +| `components/AuthButton.tsx` | `features/auth/ui/` | 인증 기능 UI | +| `components/CardGame/` | `features/` 또는 `widgets/` | 게임 기능 | + +## Tanstack Query 패턴 + +### entities 내 쿼리 정의 + +```typescript +// entities/persona/model/useGetAllPersona.ts +import { getAllPersona } from '@gitanimals/api'; +import { useSuspenseQuery } from '@tanstack/react-query'; + +export const useGetAllPersona = () => + useSuspenseQuery({ + queryKey: ['persona', 'info', 'all'], + queryFn: () => getAllPersona(), + }); +``` + +### features 내 mutation 정의 + +```typescript +// features/auction/model/useRegisterProduct.ts +import { useMutation } from '@tanstack/react-query'; + +export const useRegisterProduct = (options?) => + useMutation({ + mutationFn: registerProduct, + ...options, + }); +``` + +## 점진적 마이그레이션 전략 + +이 아키텍처는 한 번에 완성할 필요 없이, **점진적으로 개선**합니다: + +1. **새 코드**는 반드시 FSD 레이어 구조를 따릅니다 +2. **기존 코드 수정 시** 해당 코드를 적절한 레이어로 이전합니다 +3. `components/`에 남아있는 코드는 터치할 때 이전합니다 +4. `serverActions/`의 코드도 관련 `features/*/api/`로 이전합니다 diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs index eee3d643..b2c728e8 100644 --- a/apps/web/next.config.mjs +++ b/apps/web/next.config.mjs @@ -6,7 +6,7 @@ import createNextIntlPlugin from 'next-intl/plugin'; import NextPWA from 'next-pwa'; -const withNextIntl = createNextIntlPlugin(); +const withNextIntl = createNextIntlPlugin('./src/shared/i18n/request.ts'); const withPWA = NextPWA({ dest: 'public', @@ -16,7 +16,6 @@ const withPWA = NextPWA({ }); const nextConfig = withNextIntl({ - transpilePackages: ['@gitanimals/ui-panda'], reactStrictMode: true, compiler: { styledComponents: true, diff --git a/apps/web/package.json b/apps/web/package.json index 69832768..4e98e49a 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -3,9 +3,7 @@ "version": "0.1.0", "private": false, "scripts": { - "prepare": "panda codegen", "dev": "rm -rf .next && next dev --turbo", - "prebuild": "panda codegen", "build": "next build", "start": "next start", "lint": "next lint", diff --git a/apps/web/panda.config.ts b/apps/web/panda.config.ts deleted file mode 100644 index f22a5af1..00000000 --- a/apps/web/panda.config.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { keyframes, media, semanticTokens, textStyles, tokens } from '@gitanimals/ui-panda'; -import { defineConfig } from '@pandacss/dev'; - -export default defineConfig({ - // Whether to use css reset - preflight: true, - - // Where to look for your css declarations - include: ['./src/**/*.{ts,tsx}', '../../packages/ui/panda/src/**/*.{ts,tsx}'], - - // Files to exclude - exclude: [], - - // Useful for theme customization - theme: { - extend: { - tokens, - semanticTokens, - textStyles, - keyframes, - }, - }, - patterns: { - extend: {}, - }, - conditions: { - extend: { - mobile: media.mobile, - desktop: media.desktop, - tablet: media.tablet, - pc: media.pc, - }, - }, - syntax: 'object-literal', - jsxFramework: 'react', - - // The output directory for your css system - outdir: 'styled-system', - - // delete default presets - presets: ['@shadow-panda/preset'], -}); diff --git a/apps/web/postcss.config.cjs b/apps/web/postcss.config.cjs index 573efad2..96bb01e7 100644 --- a/apps/web/postcss.config.cjs +++ b/apps/web/postcss.config.cjs @@ -1,5 +1,6 @@ module.exports = { plugins: { - '@pandacss/dev/postcss': {}, + tailwindcss: {}, + autoprefixer: {}, }, } \ No newline at end of file diff --git a/apps/web/src/app/(noLocale)/auth/page.tsx b/apps/web/src/app/(noLocale)/auth/page.tsx index 61f33d59..dd9927f7 100644 --- a/apps/web/src/app/(noLocale)/auth/page.tsx +++ b/apps/web/src/app/(noLocale)/auth/page.tsx @@ -2,7 +2,7 @@ import { cookies } from 'next/headers'; import { redirect } from 'next/navigation'; import notFound from '@/app/[locale]/not-found'; -import { COOKIE_KEY } from '@/constants/storage'; +import { COOKIE_KEY } from '@/shared/config/storage'; export default async function AuthPage({ searchParams }: { searchParams: Promise<{ jwt: string }> }) { const jwtToken = (await searchParams).jwt; diff --git a/apps/web/src/app/[locale]/auth/LoginButton.tsx b/apps/web/src/app/[locale]/auth/LoginButton.tsx index 4cbef02f..56c9f166 100644 --- a/apps/web/src/app/[locale]/auth/LoginButton.tsx +++ b/apps/web/src/app/[locale]/auth/LoginButton.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { signIn } from 'next-auth/react'; -import { LOCAL_STORAGE_KEY } from '@/constants/storage'; +import { LOCAL_STORAGE_KEY } from '@/shared/config/storage'; function LoginButton({ token }: { token: string }) { const ref = React.useRef(null); diff --git a/apps/web/src/app/[locale]/auth/page.tsx b/apps/web/src/app/[locale]/auth/page.tsx index 62580010..b640762f 100644 --- a/apps/web/src/app/[locale]/auth/page.tsx +++ b/apps/web/src/app/[locale]/auth/page.tsx @@ -9,7 +9,7 @@ import { interceptorRequestFulfilled, interceptorResponseFulfilled, interceptorResponseRejected, -} from '@/apis/interceptor'; +} from '@/shared/api/interceptor'; import LoginButton from './LoginButton'; diff --git a/apps/web/src/app/[locale]/dev/Client.tsx b/apps/web/src/app/[locale]/dev/Client.tsx index 33056ff7..31f650b1 100644 --- a/apps/web/src/app/[locale]/dev/Client.tsx +++ b/apps/web/src/app/[locale]/dev/Client.tsx @@ -2,10 +2,10 @@ import { Button } from '@gitanimals/ui-tailwind'; -import { Link } from '@/i18n/routing'; -import { useDevMode } from '@/lib/devtools/devtools'; -import { useClientSession } from '@/utils/clientAuth'; -import { getIsOnLoadSheet, sendLog } from '@/utils/log'; +import { Link } from '@/shared/i18n/routing'; +import { useDevMode } from '@/shared/lib/devtools/devtools'; +import { useClientSession } from '@/shared/utils/clientAuth'; +import { getIsOnLoadSheet, sendLog } from '@/shared/utils/log'; function DevClient() { const { isDevMode } = useDevMode(); diff --git a/apps/web/src/app/[locale]/dev/cherry-blossom/page.tsx b/apps/web/src/app/[locale]/dev/cherry-blossom/page.tsx index 15eaddc3..0d7885ed 100644 --- a/apps/web/src/app/[locale]/dev/cherry-blossom/page.tsx +++ b/apps/web/src/app/[locale]/dev/cherry-blossom/page.tsx @@ -1,7 +1,5 @@ 'use client'; -import { css } from '_panda/css'; - import DevModePage from '@/components/DevMode/DevModePage'; import { CherryBlossom } from '../../landing/(spring)/CherryBlossom'; @@ -13,19 +11,19 @@ const SIZES = [12, 20, 28, 36, 48]; export default function CherryBlossomDemoPage() { return ( -
-

Cherry Blossom Particle Demo

+
+

Cherry Blossom Particle Demo

{/* Static variants */} -
-

Static Variants (sizes: {SIZES.join(', ')}px)

-
+
+

Static Variants (sizes: {SIZES.join(', ')}px)

+
{[0, 1, 2, 3].map((variant) => ( -
-

{VARIANT_LABELS[variant]}

-
+
+

{VARIANT_LABELS[variant]}

+
{SIZES.map((size) => ( -
+
- {size}px + {size}px
))}
@@ -44,9 +42,9 @@ export default function CherryBlossomDemoPage() {
{/* Animated preview */} -
-

Animated Preview

-
+
+

Animated Preview

+
{[...Array(16)].map((_, i) => { const variant = i % 4; return ( @@ -67,78 +65,3 @@ export default function CherryBlossomDemoPage() { ); } - -const pageStyle = css({ - padding: '40px', - background: 'linear-gradient(180deg, #E8F4FD 0%, #FFF0F5 40%, #FFE4EE 70%, #FFDBEE 100%)', - minHeight: '100vh', -}); - -const titleStyle = css({ - fontSize: '32px', - fontWeight: 700, - color: '#333', - marginBottom: '32px', -}); - -const subtitleStyle = css({ - fontSize: '20px', - fontWeight: 600, - color: '#555', - marginBottom: '16px', -}); - -const sectionStyle = css({ - marginBottom: '48px', -}); - -const gridStyle = css({ - display: 'flex', - flexDirection: 'column', - gap: '24px', -}); - -const cardStyle = css({ - background: 'rgba(255,255,255,0.7)', - borderRadius: '12px', - padding: '20px', -}); - -const labelStyle = css({ - fontSize: '14px', - fontWeight: 600, - color: '#666', - marginBottom: '12px', -}); - -const rowStyle = css({ - display: 'flex', - alignItems: 'flex-end', - gap: '32px', -}); - -const itemStyle = css({ - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - gap: '8px', - position: 'relative', - width: '60px', - height: '60px', -}); - -const sizeLabelStyle = css({ - fontSize: '11px', - color: '#999', - position: 'absolute', - bottom: '-4px', -}); - -const previewStyle = css({ - position: 'relative', - width: '100%', - height: '400px', - overflow: 'hidden', - borderRadius: '12px', - background: 'linear-gradient(180deg, #E8F4FD 0%, #FFF0F5 40%, #FFE4EE 70%, #FFDBEE 100%)', -}); diff --git a/apps/web/src/app/[locale]/dev/page.tsx b/apps/web/src/app/[locale]/dev/page.tsx index dc48574f..ad525350 100644 --- a/apps/web/src/app/[locale]/dev/page.tsx +++ b/apps/web/src/app/[locale]/dev/page.tsx @@ -3,7 +3,7 @@ import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@gitanimals/ui-tailwind'; import DevModePage from '@/components/DevMode/DevModePage'; -import GNB from '@/components/GNB/GNB'; +import GNB from '@/widgets/gnb/GNB'; import DevClient from './Client'; diff --git a/apps/web/src/app/[locale]/dev/token/page.tsx b/apps/web/src/app/[locale]/dev/token/page.tsx index d800fb26..f6c0139c 100644 --- a/apps/web/src/app/[locale]/dev/token/page.tsx +++ b/apps/web/src/app/[locale]/dev/token/page.tsx @@ -2,8 +2,8 @@ import { Button } from '@gitanimals/ui-tailwind'; -import { useClientSession } from '@/utils/clientAuth'; -import { copyClipBoard } from '@/utils/copy'; +import { useClientSession } from '@/shared/utils/clientAuth'; +import { copyClipBoard } from '@/shared/utils/copy'; import * as styles from './token.style'; diff --git a/apps/web/src/app/[locale]/error.tsx b/apps/web/src/app/[locale]/error.tsx index a9d3fe50..fc399bec 100644 --- a/apps/web/src/app/[locale]/error.tsx +++ b/apps/web/src/app/[locale]/error.tsx @@ -4,11 +4,11 @@ import { useEffect } from 'react'; import { useTranslations } from 'next-intl'; import { Button } from '@gitanimals/ui-tailwind'; -import { sendMessageToErrorChannel } from '@/apis/slack/sendMessage'; +import { sendMessageToErrorChannel } from '@/shared/api/slack'; import { ErrorPage } from '@/components/Error/ErrorPage'; -import { isDev } from '@/constants/env'; -import { usePathname, useRouter } from '@/i18n/routing'; -import { useClientUser } from '@/utils/clientAuth'; +import { isDev } from '@/shared/config/env'; +import { usePathname, useRouter } from '@/shared/i18n/routing'; +import { useClientUser } from '@/shared/utils/clientAuth'; interface Props { error: Error; diff --git a/apps/web/src/app/[locale]/event/(christmas)/ChristmasCard.tsx b/apps/web/src/app/[locale]/event/(christmas)/ChristmasCard.tsx index 29def3a1..a682aa77 100644 --- a/apps/web/src/app/[locale]/event/(christmas)/ChristmasCard.tsx +++ b/apps/web/src/app/[locale]/event/(christmas)/ChristmasCard.tsx @@ -2,7 +2,7 @@ import Image from 'next/image'; -import { getPersonaImage } from '@/utils/image'; +import { getPersonaImage } from '@/shared/utils/image'; interface ChristmasCardProps { type: string; diff --git a/apps/web/src/app/[locale]/event/(common)/BackgroundSection.tsx b/apps/web/src/app/[locale]/event/(common)/BackgroundSection.tsx index 4b1b57a2..1f8fe339 100644 --- a/apps/web/src/app/[locale]/event/(common)/BackgroundSection.tsx +++ b/apps/web/src/app/[locale]/event/(common)/BackgroundSection.tsx @@ -12,10 +12,10 @@ import { AxiosError } from 'axios'; import { toast } from 'sonner'; import EmblaCarousel from '@/components/EmblaCarousel'; -import { trackEvent } from '@/lib/analytics'; -import { useClientUser } from '@/utils/clientAuth'; -import { getBackgroundImage } from '@/utils/image'; -import { addNumberComma } from '@/utils/number'; +import { trackEvent } from '@/shared/lib/analytics'; +import { useClientUser } from '@/shared/utils/clientAuth'; +import { getBackgroundImage } from '@/shared/utils/image'; +import { addNumberComma } from '@/shared/utils/number'; interface BackgroundSectionProps { possibleBgTypes: string[]; diff --git a/apps/web/src/app/[locale]/event/(common)/Draw.tsx b/apps/web/src/app/[locale]/event/(common)/Draw.tsx index 793ed432..b674278e 100644 --- a/apps/web/src/app/[locale]/event/(common)/Draw.tsx +++ b/apps/web/src/app/[locale]/event/(common)/Draw.tsx @@ -14,11 +14,11 @@ import type { Variants } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion'; import { toast } from 'sonner'; -import { sendMessageToErrorChannel } from '@/apis/slack/sendMessage'; +import { sendMessageToErrorChannel } from '@/shared/api/slack'; import { login } from '@/components/AuthButton'; -import { GIT_ANIMALS_MAIN_URL } from '@/constants/outlink'; -import { Link, useRouter } from '@/i18n/routing'; -import { trackEvent } from '@/lib/analytics'; +import { GIT_ANIMALS_MAIN_URL } from '@/shared/config/outlink'; +import { Link, useRouter } from '@/shared/i18n/routing'; +import { trackEvent } from '@/shared/lib/analytics'; interface DrawProps { renderCard: (type: string) => React.ReactNode; diff --git a/apps/web/src/app/[locale]/event/(halloween)/HalloweenCard.tsx b/apps/web/src/app/[locale]/event/(halloween)/HalloweenCard.tsx index 9cb84ece..01ca1074 100644 --- a/apps/web/src/app/[locale]/event/(halloween)/HalloweenCard.tsx +++ b/apps/web/src/app/[locale]/event/(halloween)/HalloweenCard.tsx @@ -2,7 +2,7 @@ import Image from 'next/image'; -import { getPersonaImage } from '@/utils/image'; +import { getPersonaImage } from '@/shared/utils/image'; interface HalloweenCardProps { type: string; diff --git a/apps/web/src/app/[locale]/event/[eventCode]/layout.tsx b/apps/web/src/app/[locale]/event/[eventCode]/layout.tsx index 8e6f1b27..67ce0c63 100644 --- a/apps/web/src/app/[locale]/event/[eventCode]/layout.tsx +++ b/apps/web/src/app/[locale]/event/[eventCode]/layout.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import GNB from '@/components/GNB/GNB'; +import GNB from '@/widgets/gnb/GNB'; export async function generateMetadata({ params }: { params: { eventCode: string } }) { return { diff --git a/apps/web/src/app/[locale]/game/page.tsx b/apps/web/src/app/[locale]/game/page.tsx index 7b7f6f0e..654e85cd 100644 --- a/apps/web/src/app/[locale]/game/page.tsx +++ b/apps/web/src/app/[locale]/game/page.tsx @@ -1,7 +1,7 @@ 'use client'; -import { ROUTE } from '@/constants/route'; -import { redirect } from '@/i18n/routing'; +import { ROUTE } from '@/shared/config/route'; +import { redirect } from '@/shared/i18n/routing'; function GamePage() { redirect(ROUTE.GAME.QUIZ.MAIN()); diff --git a/apps/web/src/app/[locale]/game/quiz/_components/CreateOrSolve/SelectQuizType.tsx b/apps/web/src/app/[locale]/game/quiz/_components/CreateOrSolve/SelectQuizType.tsx index 09a1b3ef..08917474 100644 --- a/apps/web/src/app/[locale]/game/quiz/_components/CreateOrSolve/SelectQuizType.tsx +++ b/apps/web/src/app/[locale]/game/quiz/_components/CreateOrSolve/SelectQuizType.tsx @@ -4,9 +4,9 @@ import { useTranslations } from 'next-intl'; import { wrap } from '@suspensive/react'; import { overlay } from 'overlay-kit'; -import { ConfirmDialog } from '@/app/[locale]/laboratory/_component/ConfirmDialog'; -import { ROUTE } from '@/constants/route'; -import { useRouter } from '@/i18n/routing'; +import { ConfirmDialog } from '@gitanimals/ui-tailwind'; +import { ROUTE } from '@/shared/config/route'; +import { useRouter } from '@/shared/i18n/routing'; import useTodayQuizData from '../../_hooks/useTodayQuizData'; import { customT } from '../../_utils/quiz.intl'; diff --git a/apps/web/src/app/[locale]/game/quiz/_components/MobileLayout.tsx b/apps/web/src/app/[locale]/game/quiz/_components/MobileLayout.tsx index 3817819c..834586e2 100644 --- a/apps/web/src/app/[locale]/game/quiz/_components/MobileLayout.tsx +++ b/apps/web/src/app/[locale]/game/quiz/_components/MobileLayout.tsx @@ -3,7 +3,7 @@ import Image from 'next/image'; import { getTranslations } from 'next-intl/server'; import { cn } from '@gitanimals/ui-tailwind/utils'; -import GNB from '@/components/GNB/GNB'; +import GNB from '@/widgets/gnb/GNB'; interface MobileLayoutProps { background?: { @@ -43,7 +43,7 @@ export const MobileLayout = async ({ children, background }: MobileLayoutProps) opacity: background?.opacity ?? 0.3, }} /> -
+
{ error?: boolean; diff --git a/apps/web/src/app/[locale]/game/quiz/create/page.tsx b/apps/web/src/app/[locale]/game/quiz/create/page.tsx index 69473a42..fc0d7f82 100644 --- a/apps/web/src/app/[locale]/game/quiz/create/page.tsx +++ b/apps/web/src/app/[locale]/game/quiz/create/page.tsx @@ -1,8 +1,8 @@ import { getTranslations } from 'next-intl/server'; import { ChevronLeft } from 'lucide-react'; -import { ROUTE } from '@/constants/route'; -import { Link } from '@/i18n/routing'; +import { ROUTE } from '@/shared/config/route'; +import { Link } from '@/shared/i18n/routing'; import QuizCreateForm from './_components/QuizCreateForm'; diff --git a/apps/web/src/app/[locale]/game/quiz/solve/_components/notStarted/SelectCategorySection.tsx b/apps/web/src/app/[locale]/game/quiz/solve/_components/notStarted/SelectCategorySection.tsx index a10e3b34..ecee4276 100644 --- a/apps/web/src/app/[locale]/game/quiz/solve/_components/notStarted/SelectCategorySection.tsx +++ b/apps/web/src/app/[locale]/game/quiz/solve/_components/notStarted/SelectCategorySection.tsx @@ -12,8 +12,8 @@ import Tabs from '@/components/Tabs/Tabs'; import TabsList from '@/components/Tabs/TabsList'; import TabsTrigger from '@/components/Tabs/TabsTrigger'; import useTabs from '@/components/Tabs/useTabs'; -import { ROUTE } from '@/constants/route'; -import { type Locale, useRouter } from '@/i18n/routing'; +import { ROUTE } from '@/shared/config/route'; +import { type Locale, useRouter } from '@/shared/i18n/routing'; import type { QuizCategory } from '../../../_constants/quiz.constants'; import { QUIZ_CATEGORY } from '../../../_constants/quiz.constants'; diff --git a/apps/web/src/app/[locale]/game/quiz/solve/_components/solving/SolvingQuizSection.tsx b/apps/web/src/app/[locale]/game/quiz/solve/_components/solving/SolvingQuizSection.tsx index 0478edcf..2fbd4fc8 100644 --- a/apps/web/src/app/[locale]/game/quiz/solve/_components/solving/SolvingQuizSection.tsx +++ b/apps/web/src/app/[locale]/game/quiz/solve/_components/solving/SolvingQuizSection.tsx @@ -12,7 +12,7 @@ import FailAlertDialog from '@/app/[locale]/game/quiz/solve/_components/fail/Fai import QuizProgressBar from '@/app/[locale]/game/quiz/solve/_components/solving/QuizProgressBar'; import CorrectConfirmDialog from '@/app/[locale]/game/quiz/solve/_components/success/CorrectConfirmDialog'; import { QUIZ_ANSWER } from '@/app/[locale]/game/quiz/solve/_constants/solveQuiz.constants'; -import { customScrollStyle } from '@/styles/scrollStyle'; +import { customScrollStyle } from '@/shared/styles/scrollStyle'; import useQuizAction from '../../_hooks/useQuizAction'; import useQuizData from '../../_hooks/useQuizData'; diff --git a/apps/web/src/app/[locale]/game/quiz/solve/_hooks/useQuizAction.ts b/apps/web/src/app/[locale]/game/quiz/solve/_hooks/useQuizAction.ts index 6e9c9e8a..b2915c60 100644 --- a/apps/web/src/app/[locale]/game/quiz/solve/_hooks/useQuizAction.ts +++ b/apps/web/src/app/[locale]/game/quiz/solve/_hooks/useQuizAction.ts @@ -5,8 +5,8 @@ import { answerQuiz, getRoundResult, stopQuizContext } from '@gitanimals/api'; import { toast } from 'sonner'; import type { QuizAnswer } from '@/app/[locale]/game/quiz/solve/_constants/solveQuiz.constants'; -import { ROUTE } from '@/constants/route'; -import { type Locale, useRouter } from '@/i18n/routing'; +import { ROUTE } from '@/shared/config/route'; +import { type Locale, useRouter } from '@/shared/i18n/routing'; import { QUIZ_RESULT } from '../../_constants/quiz.constants'; import { customT } from '../../_utils/quiz.intl'; diff --git a/apps/web/src/app/[locale]/game/quiz/solve/_hooks/useQuizData.ts b/apps/web/src/app/[locale]/game/quiz/solve/_hooks/useQuizData.ts index 4ca3d881..d7b8057c 100644 --- a/apps/web/src/app/[locale]/game/quiz/solve/_hooks/useQuizData.ts +++ b/apps/web/src/app/[locale]/game/quiz/solve/_hooks/useQuizData.ts @@ -2,7 +2,7 @@ import { useLocale } from 'next-intl'; import { quizQueries } from '@gitanimals/react-query'; import { useSuspenseQuery } from '@tanstack/react-query'; -import type { Locale } from '@/i18n/routing'; +import type { Locale } from '@/shared/i18n/routing'; interface Props { contextId: string; diff --git a/apps/web/src/app/[locale]/guild/(subpage)/[id]/CopyGuildImgButton.tsx b/apps/web/src/app/[locale]/guild/(subpage)/[id]/CopyGuildImgButton.tsx index a1e08796..0449bb5e 100644 --- a/apps/web/src/app/[locale]/guild/(subpage)/[id]/CopyGuildImgButton.tsx +++ b/apps/web/src/app/[locale]/guild/(subpage)/[id]/CopyGuildImgButton.tsx @@ -4,8 +4,8 @@ import { useTranslations } from 'next-intl'; import { Button } from '@gitanimals/ui-tailwind'; import { toast } from 'sonner'; -import { getGuildString } from '@/components/Gitanimals'; -import { copyClipBoard } from '@/utils/copy'; +import { getGuildString } from '@/shared/ui/Gitanimals'; +import { copyClipBoard } from '@/shared/utils/copy'; export function CopyGuildImgButton({ guildId }: { guildId: string }) { const t = useTranslations('Preview'); diff --git a/apps/web/src/app/[locale]/guild/(subpage)/[id]/GuildSliderContainer.tsx b/apps/web/src/app/[locale]/guild/(subpage)/[id]/GuildSliderContainer.tsx index bfad51a4..23e8ec61 100644 --- a/apps/web/src/app/[locale]/guild/(subpage)/[id]/GuildSliderContainer.tsx +++ b/apps/web/src/app/[locale]/guild/(subpage)/[id]/GuildSliderContainer.tsx @@ -7,7 +7,7 @@ import { cn } from '@gitanimals/ui-tailwind'; import { wrap } from '@suspensive/react'; import { useQuery } from '@tanstack/react-query'; -import { Link } from '@/i18n/routing'; +import { Link } from '@/shared/i18n/routing'; export const GuildSliderContainer = wrap.ErrorBoundary({ fallback: <>{} }).on( ({ diff --git a/apps/web/src/app/[locale]/guild/(subpage)/[id]/MoreMenu.tsx b/apps/web/src/app/[locale]/guild/(subpage)/[id]/MoreMenu.tsx index 4c6c8a6e..bb445f10 100644 --- a/apps/web/src/app/[locale]/guild/(subpage)/[id]/MoreMenu.tsx +++ b/apps/web/src/app/[locale]/guild/(subpage)/[id]/MoreMenu.tsx @@ -17,10 +17,10 @@ import { CatIcon, EllipsisVerticalIcon, LinkIcon, LogOutIcon, SettingsIcon, User import { overlay } from 'overlay-kit'; import { toast } from 'sonner'; -import { ORIGIN_URL, ROUTE } from '@/constants/route'; -import { Link, useRouter } from '@/i18n/routing'; -import { useClientUser } from '@/utils/clientAuth'; -import { copyClipBoard } from '@/utils/copy'; +import { ORIGIN_URL, ROUTE } from '@/shared/config/route'; +import { Link, useRouter } from '@/shared/i18n/routing'; +import { useClientUser } from '@/shared/utils/clientAuth'; +import { copyClipBoard } from '@/shared/utils/copy'; interface MenuType { title: string; diff --git a/apps/web/src/app/[locale]/guild/(subpage)/[id]/page.tsx b/apps/web/src/app/[locale]/guild/(subpage)/[id]/page.tsx index 98c398ef..9dd2533f 100644 --- a/apps/web/src/app/[locale]/guild/(subpage)/[id]/page.tsx +++ b/apps/web/src/app/[locale]/guild/(subpage)/[id]/page.tsx @@ -4,8 +4,8 @@ import { Button } from '@gitanimals/ui-tailwind'; import { cn } from '@gitanimals/ui-tailwind/utils'; import { SearchIcon } from 'lucide-react'; -import { GitanimalsGuild } from '@/components/Gitanimals'; -import { Link } from '@/i18n/routing'; +import { GitanimalsGuild } from '@/shared/ui/Gitanimals'; +import { Link } from '@/shared/i18n/routing'; import { GuildPeopleList } from '../../_components/GuildPeopleList'; diff --git a/apps/web/src/app/[locale]/guild/(subpage)/[id]/setting/GuidlInfoFormClient.tsx b/apps/web/src/app/[locale]/guild/(subpage)/[id]/setting/GuidlInfoFormClient.tsx index 8f410ad5..9945f126 100644 --- a/apps/web/src/app/[locale]/guild/(subpage)/[id]/setting/GuidlInfoFormClient.tsx +++ b/apps/web/src/app/[locale]/guild/(subpage)/[id]/setting/GuidlInfoFormClient.tsx @@ -6,7 +6,7 @@ import { cn } from '@gitanimals/ui-tailwind'; import { TextArea, TextField, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@gitanimals/ui-tailwind'; import { InfoIcon } from 'lucide-react'; -import { getBackgroundImage } from '@/utils/image'; +import { getBackgroundImage } from '@/shared/utils/image'; export interface FormState { message: string; diff --git a/apps/web/src/app/[locale]/guild/(subpage)/[id]/setting/GuildSetting.tsx b/apps/web/src/app/[locale]/guild/(subpage)/[id]/setting/GuildSetting.tsx index 82390b41..457aa5e3 100644 --- a/apps/web/src/app/[locale]/guild/(subpage)/[id]/setting/GuildSetting.tsx +++ b/apps/web/src/app/[locale]/guild/(subpage)/[id]/setting/GuildSetting.tsx @@ -5,7 +5,7 @@ import { type Guild, updateGuild } from '@gitanimals/api'; import { cn } from '@gitanimals/ui-tailwind'; import { Button } from '@gitanimals/ui-tailwind'; -import { useRouter } from '@/i18n/routing'; +import { useRouter } from '@/shared/i18n/routing'; import { GuildInfoFormClient } from './GuidlInfoFormClient'; diff --git a/apps/web/src/app/[locale]/guild/(subpage)/[id]/setting/member/MemberCard.tsx b/apps/web/src/app/[locale]/guild/(subpage)/[id]/setting/member/MemberCard.tsx index ec3a4ecc..75b64d14 100644 --- a/apps/web/src/app/[locale]/guild/(subpage)/[id]/setting/member/MemberCard.tsx +++ b/apps/web/src/app/[locale]/guild/(subpage)/[id]/setting/member/MemberCard.tsx @@ -5,8 +5,8 @@ import { kickMemberFromGuild } from '@gitanimals/api'; import { Button } from '@gitanimals/ui-tailwind'; import { toast } from 'sonner'; -import { useRouter } from '@/i18n/routing'; -import { getPersonaImage } from '@/utils/image'; +import { useRouter } from '@/shared/i18n/routing'; +import { getPersonaImage } from '@/shared/utils/image'; import { BannerGuildMember } from './BannerGuildMember'; diff --git a/apps/web/src/app/[locale]/guild/(subpage)/[id]/setting/member/WaitMemberCard.tsx b/apps/web/src/app/[locale]/guild/(subpage)/[id]/setting/member/WaitMemberCard.tsx index a0dbdc56..4121ae4d 100644 --- a/apps/web/src/app/[locale]/guild/(subpage)/[id]/setting/member/WaitMemberCard.tsx +++ b/apps/web/src/app/[locale]/guild/(subpage)/[id]/setting/member/WaitMemberCard.tsx @@ -4,8 +4,8 @@ import { acceptJoinGuild, denyJoinGuild, type GuildMember } from '@gitanimals/ap import { Button } from '@gitanimals/ui-tailwind'; import { toast } from 'sonner'; -import { useRouter } from '@/i18n/routing'; -import { getPersonaImage } from '@/utils/image'; +import { useRouter } from '@/shared/i18n/routing'; +import { getPersonaImage } from '@/shared/utils/image'; import { BannerGuildMember } from './BannerGuildMember'; diff --git a/apps/web/src/app/[locale]/guild/(subpage)/[id]/setting/page.tsx b/apps/web/src/app/[locale]/guild/(subpage)/[id]/setting/page.tsx index 0fe4a3cb..f6b8bc0c 100644 --- a/apps/web/src/app/[locale]/guild/(subpage)/[id]/setting/page.tsx +++ b/apps/web/src/app/[locale]/guild/(subpage)/[id]/setting/page.tsx @@ -1,7 +1,7 @@ import { checkIsLeader, getGuildBackgrounds, getGuildById, getGuildIcons } from '@gitanimals/api'; import { PageModalTitle } from '@/components/PageModal'; -import { redirect } from '@/i18n/routing'; +import { redirect } from '@/shared/i18n/routing'; import { GuildSetting } from './GuildSetting'; diff --git a/apps/web/src/app/[locale]/guild/(subpage)/[id]/setting/pet/page.tsx b/apps/web/src/app/[locale]/guild/(subpage)/[id]/setting/pet/page.tsx index 8b9eca7a..4eb65358 100644 --- a/apps/web/src/app/[locale]/guild/(subpage)/[id]/setting/pet/page.tsx +++ b/apps/web/src/app/[locale]/guild/(subpage)/[id]/setting/pet/page.tsx @@ -6,7 +6,7 @@ import { cn } from '@gitanimals/ui-tailwind'; import { GuildModalPageTitle } from '@/app/[locale]/guild/_components/GuildModalPageLayout'; import { GuildJoinPetSelectDialog } from '@/app/[locale]/guild/_components/GuildPetSelectDialog'; -import { useRouter } from '@/i18n/routing'; +import { useRouter } from '@/shared/i18n/routing'; export default function GuildSettingPetPage() { const router = useRouter(); diff --git a/apps/web/src/app/[locale]/guild/(subpage)/create/GuildCreate.tsx b/apps/web/src/app/[locale]/guild/(subpage)/create/GuildCreate.tsx index 94e045a8..60bfd03b 100644 --- a/apps/web/src/app/[locale]/guild/(subpage)/create/GuildCreate.tsx +++ b/apps/web/src/app/[locale]/guild/(subpage)/create/GuildCreate.tsx @@ -5,7 +5,7 @@ import { Button } from '@gitanimals/ui-tailwind'; import { AxiosError } from 'axios'; import { toast } from 'sonner'; -import { useRouter } from '@/i18n/routing'; +import { useRouter } from '@/shared/i18n/routing'; import { SelectPersonaList } from '../../_components/SelectPersonaList'; diff --git a/apps/web/src/app/[locale]/guild/(subpage)/create/page.tsx b/apps/web/src/app/[locale]/guild/(subpage)/create/page.tsx index 41a3d054..27758604 100644 --- a/apps/web/src/app/[locale]/guild/(subpage)/create/page.tsx +++ b/apps/web/src/app/[locale]/guild/(subpage)/create/page.tsx @@ -8,7 +8,7 @@ import GuildCreate from './GuildCreate'; const dialogTitleStyle = cn( 'font-product text-glyph-48 font-bold text-white text-center', - 'max-[1200px]:text-glyph-32', + 'max-1200:text-glyph-32', 'max-mobile:text-glyph-24', ); diff --git a/apps/web/src/app/[locale]/guild/(subpage)/detail/GuildDetail.tsx b/apps/web/src/app/[locale]/guild/(subpage)/detail/GuildDetail.tsx index 95dbe759..662eea7b 100644 --- a/apps/web/src/app/[locale]/guild/(subpage)/detail/GuildDetail.tsx +++ b/apps/web/src/app/[locale]/guild/(subpage)/detail/GuildDetail.tsx @@ -4,7 +4,7 @@ import type { Guild } from '@gitanimals/api'; import { cn } from '@gitanimals/ui-tailwind'; -import { GitanimalsGuild } from '@/components/Gitanimals'; +import { GitanimalsGuild } from '@/shared/ui/Gitanimals'; import { GuildPeopleList } from '../../_components/GuildPeopleList'; diff --git a/apps/web/src/app/[locale]/guild/(subpage)/detail/[id]/join/page.tsx b/apps/web/src/app/[locale]/guild/(subpage)/detail/[id]/join/page.tsx index 3a58582d..e5311de2 100644 --- a/apps/web/src/app/[locale]/guild/(subpage)/detail/[id]/join/page.tsx +++ b/apps/web/src/app/[locale]/guild/(subpage)/detail/[id]/join/page.tsx @@ -5,12 +5,12 @@ import { cn } from '@gitanimals/ui-tailwind'; import { useQueryClient } from '@tanstack/react-query'; import { GuildJoinPetSelectDialog } from '@/app/[locale]/guild/_components/GuildPetSelectDialog'; -import { useRouter } from '@/i18n/routing'; +import { useRouter } from '@/shared/i18n/routing'; import { joinGuildAction } from '@/serverActions/guild'; const dialogTitleStyle = cn( 'font-product text-glyph-48 font-bold text-white text-center', - 'max-[1200px]:text-glyph-32', + 'max-1200:text-glyph-32', 'max-mobile:text-glyph-24', ); diff --git a/apps/web/src/app/[locale]/guild/(subpage)/detail/[id]/page.tsx b/apps/web/src/app/[locale]/guild/(subpage)/detail/[id]/page.tsx index f41b3a83..8d3a2f50 100644 --- a/apps/web/src/app/[locale]/guild/(subpage)/detail/[id]/page.tsx +++ b/apps/web/src/app/[locale]/guild/(subpage)/detail/[id]/page.tsx @@ -1,7 +1,7 @@ import { checkIsMyGuild, getGuildById } from '@gitanimals/api'; import { Button } from '@gitanimals/ui-tailwind'; -import { Link } from '@/i18n/routing'; +import { Link } from '@/shared/i18n/routing'; import { GuildDetail } from '../GuildDetail'; diff --git a/apps/web/src/app/[locale]/guild/@modal/(.)detail/[id]/join/page.tsx b/apps/web/src/app/[locale]/guild/@modal/(.)detail/[id]/join/page.tsx index 928fc910..f280af77 100644 --- a/apps/web/src/app/[locale]/guild/@modal/(.)detail/[id]/join/page.tsx +++ b/apps/web/src/app/[locale]/guild/@modal/(.)detail/[id]/join/page.tsx @@ -9,7 +9,7 @@ import { toast } from 'sonner'; import { GuildJoinPetSelectDialog } from '@/app/[locale]/guild/_components/GuildPetSelectDialog'; import RouteModal, { RouteModalTitle } from '@/components/RouteModal'; -import { useRouter } from '@/i18n/routing'; +import { useRouter } from '@/shared/i18n/routing'; import { joinGuildAction } from '@/serverActions/guild'; export default function GuildJoinModal({ params }: { params: { id: string } }) { diff --git a/apps/web/src/app/[locale]/guild/@modal/(.)detail/[id]/page.tsx b/apps/web/src/app/[locale]/guild/@modal/(.)detail/[id]/page.tsx index b02906f3..c5758458 100644 --- a/apps/web/src/app/[locale]/guild/@modal/(.)detail/[id]/page.tsx +++ b/apps/web/src/app/[locale]/guild/@modal/(.)detail/[id]/page.tsx @@ -3,7 +3,7 @@ import { Button } from '@gitanimals/ui-tailwind'; import { cn } from '@gitanimals/ui-tailwind/utils'; import RouteModal from '@/components/RouteModal'; -import { Link } from '@/i18n/routing'; +import { Link } from '@/shared/i18n/routing'; import { GuildDetail } from '../../../(subpage)/detail/GuildDetail'; diff --git a/apps/web/src/app/[locale]/guild/_components/GuildCard.tsx b/apps/web/src/app/[locale]/guild/_components/GuildCard.tsx index 06952c74..a301e395 100644 --- a/apps/web/src/app/[locale]/guild/_components/GuildCard.tsx +++ b/apps/web/src/app/[locale]/guild/_components/GuildCard.tsx @@ -5,8 +5,8 @@ import { type Guild } from '@gitanimals/api'; import { cn } from '@gitanimals/ui-tailwind'; import { UsersRoundIcon } from 'lucide-react'; -import { ROUTE } from '@/constants/route'; -import { Link } from '@/i18n/routing'; +import { ROUTE } from '@/shared/config/route'; +import { Link } from '@/shared/i18n/routing'; interface GuildCardProps { guild: Guild; diff --git a/apps/web/src/app/[locale]/guild/_components/GuildModalPageLayout.tsx b/apps/web/src/app/[locale]/guild/_components/GuildModalPageLayout.tsx index f80719b7..92a13bb6 100644 --- a/apps/web/src/app/[locale]/guild/_components/GuildModalPageLayout.tsx +++ b/apps/web/src/app/[locale]/guild/_components/GuildModalPageLayout.tsx @@ -5,7 +5,7 @@ import { XIcon } from 'lucide-react'; import { BackTrigger } from '@/components/Trigger'; const dialogTitleStyle = - 'font-product text-glyph-48 font-bold text-white text-center max-[1200px]:text-glyph-32 max-mobile:text-glyph-24'; + 'font-product text-glyph-48 font-bold text-white text-center max-1200:text-glyph-32 max-mobile:text-glyph-24'; export function GuildModalPageLayout({ children }: PropsWithChildren) { return ( diff --git a/apps/web/src/app/[locale]/guild/_components/GuildPeopleList.tsx b/apps/web/src/app/[locale]/guild/_components/GuildPeopleList.tsx index dc5561f5..2c581866 100644 --- a/apps/web/src/app/[locale]/guild/_components/GuildPeopleList.tsx +++ b/apps/web/src/app/[locale]/guild/_components/GuildPeopleList.tsx @@ -5,8 +5,8 @@ import type { GuildLeader, GuildMember } from '@gitanimals/api'; import useEmblaCarousel from 'embla-carousel-react'; import { UsersRoundIcon } from 'lucide-react'; -import { USER_GITHUB_URL } from '@/constants/route'; -import { getPersonaImage } from '@/utils/image'; +import { USER_GITHUB_URL } from '@/shared/config/route'; +import { getPersonaImage } from '@/shared/utils/image'; export function GuildPeopleList({ members, leader }: { members: GuildMember[]; leader: GuildLeader }) { const [emblaRef] = useEmblaCarousel({ diff --git a/apps/web/src/app/[locale]/guild/_components/GuildSearch.tsx b/apps/web/src/app/[locale]/guild/_components/GuildSearch.tsx index e231d15b..f954fbbb 100644 --- a/apps/web/src/app/[locale]/guild/_components/GuildSearch.tsx +++ b/apps/web/src/app/[locale]/guild/_components/GuildSearch.tsx @@ -5,8 +5,8 @@ import { useSearchParams } from 'next/navigation'; import { cn, SearchBar } from '@gitanimals/ui-tailwind'; import { ChevronLeftIcon } from 'lucide-react'; -import { useGetNextUrl } from '@/hooks/useGetNewUrl'; -import { useRouter } from '@/i18n/routing'; +import { useGetNextUrl } from '@/shared/hooks/useGetNewUrl'; +import { useRouter } from '@/shared/i18n/routing'; export function GuildSearch() { const router = useRouter(); diff --git a/apps/web/src/app/[locale]/guild/_components/SelectPersonaList.tsx b/apps/web/src/app/[locale]/guild/_components/SelectPersonaList.tsx index c883e38e..9078ddfd 100644 --- a/apps/web/src/app/[locale]/guild/_components/SelectPersonaList.tsx +++ b/apps/web/src/app/[locale]/guild/_components/SelectPersonaList.tsx @@ -7,11 +7,11 @@ import { cn, Skeleton } from '@gitanimals/ui-tailwind'; import { wrap } from '@suspensive/react'; import { useSuspenseQuery } from '@tanstack/react-query'; -import { MemoizedLevelPersonaItem } from '@/components/PersonaItem'; -import { PersonaListToolbar } from '@/components/PersonaListToolbar'; -import { usePersonaListFilter } from '@/hooks/persona/usePersonaListFilter'; -import { customScrollStyle } from '@/styles/scrollStyle'; -import { useClientUser } from '@/utils/clientAuth'; +import { MemoizedLevelPersonaItem } from '@/entities/persona/ui/PersonaItem'; +import { PersonaListToolbar } from '@/entities/persona/ui/PersonaListToolbar'; +import { usePersonaListFilter } from '@/entities/persona/model/usePersonaListFilter'; +import { customScrollStyle } from '@/shared/styles/scrollStyle'; +import { useClientUser } from '@/shared/utils/clientAuth'; const flexOverflowStyle = cn( 'flex overflow-y-auto overflow-x-hidden w-full gap-1 h-full min-h-0 flex-wrap max-h-full', diff --git a/apps/web/src/app/[locale]/guild/_components/SortSelect.tsx b/apps/web/src/app/[locale]/guild/_components/SortSelect.tsx index 02cd831d..676eddce 100644 --- a/apps/web/src/app/[locale]/guild/_components/SortSelect.tsx +++ b/apps/web/src/app/[locale]/guild/_components/SortSelect.tsx @@ -4,8 +4,8 @@ import { useSearchParams } from 'next/navigation'; import type { FilterType } from '@gitanimals/api'; import { Select } from '@gitanimals/ui-tailwind'; -import { useGetNextUrl } from '@/hooks/useGetNewUrl'; -import { useRouter } from '@/i18n/routing'; +import { useGetNextUrl } from '@/shared/hooks/useGetNewUrl'; +import { useRouter } from '@/shared/i18n/routing'; const options: { label: string; value: FilterType }[] = [ { label: 'Random', value: 'RANDOM' }, diff --git a/apps/web/src/app/[locale]/guild/layout.tsx b/apps/web/src/app/[locale]/guild/layout.tsx index f48478b2..964900b4 100644 --- a/apps/web/src/app/[locale]/guild/layout.tsx +++ b/apps/web/src/app/[locale]/guild/layout.tsx @@ -1,4 +1,4 @@ -import GNB from '@/components/GNB/GNB'; +import GNB from '@/widgets/gnb/GNB'; export default function GuildLayout({ children, modal }: { children: React.ReactNode; modal: React.ReactNode }) { return ( diff --git a/apps/web/src/app/[locale]/guild/page.tsx b/apps/web/src/app/[locale]/guild/page.tsx index e7db6530..29a793d2 100644 --- a/apps/web/src/app/[locale]/guild/page.tsx +++ b/apps/web/src/app/[locale]/guild/page.tsx @@ -9,8 +9,8 @@ import Image from 'next/image'; import { PaginationServer } from '@/components/Pagination/PaginationServer'; import { BackTrigger } from '@/components/Trigger'; -import { ROUTE } from '@/constants/route'; -import { Link, redirect } from '@/i18n/routing'; +import { ROUTE } from '@/shared/config/route'; +import { Link, redirect } from '@/shared/i18n/routing'; import { GuildCard } from './_components/GuildCard'; import { GuildSearch } from './_components/GuildSearch'; diff --git a/apps/web/src/app/[locale]/laboratory/_component/AlertDialog.tsx b/apps/web/src/app/[locale]/laboratory/_component/AlertDialog.tsx deleted file mode 100644 index fd310c19..00000000 --- a/apps/web/src/app/[locale]/laboratory/_component/AlertDialog.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; -import { Button, Dialog } from '@gitanimals/ui-tailwind'; - -export function AlertDialog(props: { - isOpen: boolean; - onClose: () => void; - title: string; - description: React.ReactNode; -}) { - return ( - - - {props.title} - - {props.description} - -
- -
-
-
- ); -} diff --git a/apps/web/src/app/[locale]/laboratory/_component/ConfirmDialog.tsx b/apps/web/src/app/[locale]/laboratory/_component/ConfirmDialog.tsx deleted file mode 100644 index 8b81ad0e..00000000 --- a/apps/web/src/app/[locale]/laboratory/_component/ConfirmDialog.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; -import { Button, Dialog } from '@gitanimals/ui-tailwind'; - -export function ConfirmDialog(props: { - isOpen: boolean; - onClose: () => void; - onConfirm: () => void; - title: string; - description: React.ReactNode; -}) { - return ( - - - {props.title} - - {props.description} - -
- - -
-
-
- ); -} diff --git a/apps/web/src/app/[locale]/laboratory/_component/FeedbackUpvote.tsx b/apps/web/src/app/[locale]/laboratory/_component/FeedbackUpvote.tsx index f6da5e71..62a832f0 100644 --- a/apps/web/src/app/[locale]/laboratory/_component/FeedbackUpvote.tsx +++ b/apps/web/src/app/[locale]/laboratory/_component/FeedbackUpvote.tsx @@ -6,10 +6,10 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { ArrowUp } from 'lucide-react'; import { toast } from 'sonner'; -import { createOrUpdateUpvote } from '@/apis/laboratory/feedback'; -import { LABORATORY_FEEDBACK_QUERY_KEYS, upvoteQueryOptions } from '@/apis/laboratory/useLaboratoryFeedback'; -import { usePathname } from '@/i18n/routing'; -import { useClientUser } from '@/utils/clientAuth'; +import { createOrUpdateUpvote } from '@/features/laboratory-feedback/api/feedback'; +import { LABORATORY_FEEDBACK_QUERY_KEYS, upvoteQueryOptions } from '@/features/laboratory-feedback/model/useLaboratoryFeedback'; +import { usePathname } from '@/shared/i18n/routing'; +import { useClientUser } from '@/shared/utils/clientAuth'; export function FeedbackUpvote() { const pathname = usePathname(); diff --git a/apps/web/src/app/[locale]/laboratory/_component/LaboratoryLayout.tsx b/apps/web/src/app/[locale]/laboratory/_component/LaboratoryLayout.tsx index 9bd37c1e..39c01e67 100644 --- a/apps/web/src/app/[locale]/laboratory/_component/LaboratoryLayout.tsx +++ b/apps/web/src/app/[locale]/laboratory/_component/LaboratoryLayout.tsx @@ -7,9 +7,9 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { ArrowLeft, Heart } from 'lucide-react'; import { toast } from 'sonner'; -import { createOrUpdateUpvote } from '@/apis/laboratory/feedback'; -import { LABORATORY_FEEDBACK_QUERY_KEYS, upvoteQueryOptions } from '@/apis/laboratory/useLaboratoryFeedback'; -import { useClientUser } from '@/utils/clientAuth'; +import { createOrUpdateUpvote } from '@/features/laboratory-feedback/api/feedback'; +import { LABORATORY_FEEDBACK_QUERY_KEYS, upvoteQueryOptions } from '@/features/laboratory-feedback/model/useLaboratoryFeedback'; +import { useClientUser } from '@/shared/utils/clientAuth'; interface LaboratoryLayoutProps { children: ReactNode; diff --git a/apps/web/src/app/[locale]/laboratory/layout.tsx b/apps/web/src/app/[locale]/laboratory/layout.tsx index 3c9af3ad..aa2c8668 100644 --- a/apps/web/src/app/[locale]/laboratory/layout.tsx +++ b/apps/web/src/app/[locale]/laboratory/layout.tsx @@ -1,3 +1,3 @@ export default async function LaboratoryLayout({ children }: { children: React.ReactNode }) { - return
{children}
; + return
{children}
; } diff --git a/apps/web/src/app/[locale]/laboratory/multi-merge/PersonaItem.tsx b/apps/web/src/app/[locale]/laboratory/multi-merge/PersonaItem.tsx index 6d2c1d15..06780162 100644 --- a/apps/web/src/app/[locale]/laboratory/multi-merge/PersonaItem.tsx +++ b/apps/web/src/app/[locale]/laboratory/multi-merge/PersonaItem.tsx @@ -1,4 +1,4 @@ export { MemoizedBannerPersonaItem as MemoizedPersonaBannerItem, MemoizedLevelPersonaItem as MemoizedPersonaItem, -} from '@/components/PersonaItem'; +} from '@/entities/persona/ui/PersonaItem'; diff --git a/apps/web/src/app/[locale]/laboratory/multi-merge/client.tsx b/apps/web/src/app/[locale]/laboratory/multi-merge/client.tsx index e2f95866..8f4fff74 100644 --- a/apps/web/src/app/[locale]/laboratory/multi-merge/client.tsx +++ b/apps/web/src/app/[locale]/laboratory/multi-merge/client.tsx @@ -9,8 +9,8 @@ import { useMutation, useQueryClient, useSuspenseQuery } from '@tanstack/react-q import { overlay } from 'overlay-kit'; import { toast } from 'sonner'; -import { Link } from '@/i18n/routing'; -import { useClientUser } from '@/utils/clientAuth'; +import { Link } from '@/shared/i18n/routing'; +import { useClientUser } from '@/shared/utils/clientAuth'; import { MergeResultModal } from '../../mypage/my-pet/(merge)/MergeResult'; diff --git a/apps/web/src/app/[locale]/laboratory/page.tsx b/apps/web/src/app/[locale]/laboratory/page.tsx index 0198ba9e..97a5db59 100644 --- a/apps/web/src/app/[locale]/laboratory/page.tsx +++ b/apps/web/src/app/[locale]/laboratory/page.tsx @@ -5,10 +5,10 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { Heart } from 'lucide-react'; import { toast } from 'sonner'; -import { createOrUpdateUpvote } from '@/apis/laboratory/feedback'; -import { LABORATORY_FEEDBACK_QUERY_KEYS, upvoteQueryOptions } from '@/apis/laboratory/useLaboratoryFeedback'; -import { Link } from '@/i18n/routing'; -import { useClientUser } from '@/utils/clientAuth'; +import { createOrUpdateUpvote } from '@/features/laboratory-feedback/api/feedback'; +import { LABORATORY_FEEDBACK_QUERY_KEYS, upvoteQueryOptions } from '@/features/laboratory-feedback/model/useLaboratoryFeedback'; +import { Link } from '@/shared/i18n/routing'; +import { useClientUser } from '@/shared/utils/clientAuth'; // Note: This is now a client component, metadata should be handled at layout level if needed diff --git a/apps/web/src/app/[locale]/laboratory/property-pet-sell/page.tsx b/apps/web/src/app/[locale]/laboratory/property-pet-sell/page.tsx index ee9df9a5..b3c50dfb 100644 --- a/apps/web/src/app/[locale]/laboratory/property-pet-sell/page.tsx +++ b/apps/web/src/app/[locale]/laboratory/property-pet-sell/page.tsx @@ -9,10 +9,10 @@ import { wrap } from '@suspensive/react'; import { useQueryClient, useSuspenseQuery } from '@tanstack/react-query'; import { useDialog } from '@/components/Global/useDialog'; -import { trackEvent } from '@/lib/analytics'; -import { customScrollStyle } from '@/styles/scrollStyle'; -import { useClientUser } from '@/utils/clientAuth'; -import { getPersonaImage } from '@/utils/image'; +import { trackEvent } from '@/shared/lib/analytics'; +import { customScrollStyle } from '@/shared/styles/scrollStyle'; +import { useClientUser } from '@/shared/utils/clientAuth'; +import { getPersonaImage } from '@/shared/utils/image'; import { LaboratoryLayout } from '../_component/LaboratoryLayout'; diff --git a/apps/web/src/app/[locale]/landing/(christmas)/MotionPet.tsx b/apps/web/src/app/[locale]/landing/(christmas)/MotionPet.tsx index 768f07f5..af741fab 100644 --- a/apps/web/src/app/[locale]/landing/(christmas)/MotionPet.tsx +++ b/apps/web/src/app/[locale]/landing/(christmas)/MotionPet.tsx @@ -1,5 +1,4 @@ 'use client'; -import { css } from '_panda/css'; import { motion } from 'framer-motion'; export function MotionPet() { @@ -9,13 +8,7 @@ export function MotionPet() { alt="snowman" width={100} height={100} - className={css({ - width: 'auto', - height: '100%', - objectFit: 'contain', - userSelect: 'none', - cursor: 'pointer', - })} + className="w-auto h-full object-contain select-none cursor-pointer" whileHover={{ y: [0, -20, 0] }} whileTap={{ y: [0, -20, 0] }} transition={{ diff --git a/apps/web/src/app/[locale]/landing/(christmas)/Snowflake.tsx b/apps/web/src/app/[locale]/landing/(christmas)/Snowflake.tsx index eea9a0f2..5ae87f7d 100644 --- a/apps/web/src/app/[locale]/landing/(christmas)/Snowflake.tsx +++ b/apps/web/src/app/[locale]/landing/(christmas)/Snowflake.tsx @@ -1,6 +1,5 @@ 'use client'; -import { css } from '_panda/css'; import { motion } from 'framer-motion'; interface SnowflakeProps { @@ -13,7 +12,7 @@ interface SnowflakeProps { export function Snowflake({ delay, left, size, duration }: SnowflakeProps) { return ( ); } - -const snowflakeStyle = css({ - position: 'absolute', - backgroundColor: 'white', - borderRadius: '4px', - opacity: 0.8, - pointerEvents: 'none', - top: 0, - zIndex: 1, - _mobile: { - scale: 0.8, - }, -}); diff --git a/apps/web/src/app/[locale]/landing/(christmas)/index.tsx b/apps/web/src/app/[locale]/landing/(christmas)/index.tsx index a78a5899..87335ab3 100644 --- a/apps/web/src/app/[locale]/landing/(christmas)/index.tsx +++ b/apps/web/src/app/[locale]/landing/(christmas)/index.tsx @@ -1,11 +1,10 @@ import Image from 'next/image'; -import { css } from '_panda/css'; -import { flex } from '_panda/patterns'; -import { Button } from '@gitanimals/ui-panda'; +import { Button } from '@gitanimals/ui-tailwind'; +import { cn } from '@gitanimals/ui-tailwind/utils'; -import { getServerAuth } from '@/auth'; +import { getServerAuth } from '@/shared/api/auth'; import { LoginButton } from '@/components/AuthButton'; -import { Link } from '@/i18n/routing'; +import { Link } from '@/shared/i18n/routing'; import { MotionPet } from './MotionPet'; import { Snowflake } from './Snowflake'; @@ -14,7 +13,7 @@ export async function ChristmasContent() { const session = await getServerAuth(); return (
-
+
{[...Array(20)].map((_, i) => (
-
+
gitanimals christmas event -

+

Christmas is here in Gitaniamals!
Draw a Christmas pet now!

-
+
{!session ? ( ) : ( - - @@ -94,62 +77,22 @@ export async function ChristmasContent() { ); } -const descriptionStyle = css({ - color: 'white.white_90', - textStyle: 'glyph32.bold', - fontWeight: 400, - whiteSpace: 'pre-line', - marginTop: '40px', - marginBottom: '40px', - _mobile: { - textStyle: 'glyph16.regular', - fontSize: '16px', - marginTop: '20px', - textAlign: 'center', - }, -}); +const descriptionClass = cn( + 'text-white/90 whitespace-pre-line mt-10 mb-10', + 'font-product text-glyph-32 font-bold font-normal', + 'max-mobile:mt-5 max-mobile:mb-5 max-mobile:text-center max-mobile:text-glyph-16', +); -const bgImageStyle = css({ - pointerEvents: 'none', - objectPosition: 'bottom center', -}); +const bgImageClass = 'pointer-events-none object-bottom object-center'; -const containerStyle = flex({ - position: 'relative', - width: '80%', - height: '100%', - paddingLeft: '40px', - zIndex: 2, - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - paddingBottom: '160px', - _mobile: { - width: '100%', - alignItems: 'center', - justifyContent: 'center', - paddingBottom: '100px', - pl: 0, - }, -}); +const containerClass = cn( + 'relative z-[2] w-[80%] h-full pl-10 pb-40 flex flex-col justify-center', + 'max-mobile:w-full max-mobile:items-center max-mobile:justify-center max-mobile:pb-[100px] max-mobile:pl-0', +); -const logoImageStyle = css({ - objectFit: 'contain', - height: 'auto', - _mobile: { - maxWidth: '90vw', - }, -}); +const logoImageClass = cn('object-contain h-auto', 'max-mobile:max-w-[90vw]'); -const bgContainerStyle = css({ - position: 'relative', - width: '100%', - height: 'calc(100vh - 60px )', - display: 'grid', - gridTemplateColumns: '3fr 4fr', - paddingTop: '10%', - - _mobile: { - gridTemplateColumns: '1fr', - }, -}); +const bgContainerClass = cn( + 'relative w-full h-[calc(100vh-60px)] grid grid-cols-[3fr_4fr] pt-[10%]', + 'max-mobile:grid-cols-1', +); diff --git a/apps/web/src/app/[locale]/landing/(spring)/CherryBlossom.tsx b/apps/web/src/app/[locale]/landing/(spring)/CherryBlossom.tsx index 5cd01587..9fa92dcc 100644 --- a/apps/web/src/app/[locale]/landing/(spring)/CherryBlossom.tsx +++ b/apps/web/src/app/[locale]/landing/(spring)/CherryBlossom.tsx @@ -1,6 +1,5 @@ 'use client'; -import { css } from '_panda/css'; import { motion } from 'framer-motion'; interface CherryBlossomProps { @@ -129,7 +128,7 @@ export function BlossomSVG({ variant, size }: { variant: number; size: number }) export function CherryBlossom({ delay, left, size, duration, variant, opacity }: CherryBlossomProps) { return ( ); } - -const blossomStyle = css({ - position: 'absolute', - pointerEvents: 'none', - top: 0, - zIndex: 1, - _mobile: { - scale: 0.8, - }, -}); diff --git a/apps/web/src/app/[locale]/landing/(spring)/MotionPet.tsx b/apps/web/src/app/[locale]/landing/(spring)/MotionPet.tsx index 9944a5a7..9f30031b 100644 --- a/apps/web/src/app/[locale]/landing/(spring)/MotionPet.tsx +++ b/apps/web/src/app/[locale]/landing/(spring)/MotionPet.tsx @@ -39,7 +39,7 @@ export function MotionPetSection() { return ( <> -
+
diff --git a/apps/web/src/app/[locale]/landing/(spring)/index.tsx b/apps/web/src/app/[locale]/landing/(spring)/index.tsx index a3217d46..755c414b 100644 --- a/apps/web/src/app/[locale]/landing/(spring)/index.tsx +++ b/apps/web/src/app/[locale]/landing/(spring)/index.tsx @@ -1,10 +1,9 @@ -import { css } from '_panda/css'; -import { flex } from '_panda/patterns'; -import { Button } from '@gitanimals/ui-panda'; +import { Button } from '@gitanimals/ui-tailwind'; +import { cn } from '@gitanimals/ui-tailwind/utils'; -import { getServerAuth } from '@/auth'; import { LoginButton } from '@/components/AuthButton'; -import { Link } from '@/i18n/routing'; +import { getServerAuth } from '@/shared/api/auth'; +import { Link } from '@/shared/i18n/routing'; import { CherryBlossom } from './CherryBlossom'; import { MotionPetSection } from './MotionPet'; @@ -23,7 +22,7 @@ export async function SpringContent() { const session = await getServerAuth(); return (
-
+
{[...Array(BLOSSOM_COUNT)].map((_, i) => ( -
- GITANIMALS -

+

+ GITANIMALS +

Spring is blooming in Gitanimals!
Collect a spring pet now!

-
+
{!session ? ( ) : ( - - @@ -65,64 +64,24 @@ export async function SpringContent() { ); } -const springLogoStyle = css({ - width: 'min(600px, 45vw)', - height: 'auto', - userSelect: 'none', - filter: 'drop-shadow(0 4px 12px rgba(255, 150, 170, 0.3))', - _mobile: { - width: 'min(280px, 80vw)', - }, -}); +const springLogoClass = cn( + 'w-[min(600px,45vw)] h-auto select-none drop-shadow-[0_4px_12px_rgba(255,150,170,0.3)]', + 'max-mobile:w-[min(280px,80vw)]', +); -const descriptionStyle = css({ - color: 'black.black_75', - textStyle: 'glyph32.bold', - fontWeight: 400, - whiteSpace: 'pre-line', - marginTop: '16px', - marginBottom: '28px', - lineHeight: 1.5, - _mobile: { - textStyle: 'glyph16.regular', - fontSize: '16px', - marginTop: '12px', - marginBottom: '20px', - textAlign: 'center', - }, -}); +const descriptionClass = cn( + 'text-black/75 whitespace-pre-line mt-4 mb-7 leading-[1.5]', + 'font-product text-glyph-32 font-normal', + 'max-mobile:text-glyph-16 max-mobile:text-[16px] max-mobile:mt-3 max-mobile:mb-5 max-mobile:text-center', +); -const containerStyle = flex({ - position: 'relative', - width: '80%', - height: '100%', - paddingLeft: '40px', - zIndex: 2, - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - paddingBottom: '0', - gap: '0', - _mobile: { - width: '100%', - alignItems: 'center', - justifyContent: 'center', - paddingBottom: '0', - pl: 0, - }, -}); +const containerClass = cn( + 'relative w-[80%] h-full pl-10 z-[2] flex flex-col justify-center', + 'max-mobile:w-full max-mobile:items-center max-mobile:justify-center max-mobile:pl-0', +); -const bgContainerStyle = css({ - position: 'relative', - width: '100%', - height: 'calc(100vh - 60px)', - display: 'grid', - gridTemplateColumns: '2fr 3fr', - gap: '0', - alignItems: 'center', - overflow: 'hidden', - background: 'linear-gradient(180deg, #E8F4FD 0%, #FFF0F5 40%, #FFE4EE 70%, #FFDBEE 100%)', - _mobile: { - gridTemplateColumns: '1fr', - }, -}); +const bgContainerClass = cn( + 'relative w-full h-[calc(100vh-60px)] grid grid-cols-[2fr_3fr] items-center overflow-hidden', + 'bg-gradient-to-b from-[#E8F4FD] via-[#FFF0F5] via-[70%] to-[#FFDBEE]', + 'max-mobile:grid-cols-1', +); diff --git a/apps/web/src/app/[locale]/landing/AvailablePetSection/AnimalCardList.tsx b/apps/web/src/app/[locale]/landing/AvailablePetSection/AnimalCardList.tsx index e4c421b2..46ea753a 100644 --- a/apps/web/src/app/[locale]/landing/AvailablePetSection/AnimalCardList.tsx +++ b/apps/web/src/app/[locale]/landing/AvailablePetSection/AnimalCardList.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { cn } from '@gitanimals/ui-tailwind/utils'; -import { AnimalCard } from '@/components/AnimalCard'; +import { AnimalCard } from '@/entities/persona'; const ANIMAL_LIST = [ { diff --git a/apps/web/src/app/[locale]/landing/AvailablePetSection/AnimalSlider.style.ts b/apps/web/src/app/[locale]/landing/AvailablePetSection/AnimalSlider.style.ts index 53c2150b..359c2bb8 100644 --- a/apps/web/src/app/[locale]/landing/AvailablePetSection/AnimalSlider.style.ts +++ b/apps/web/src/app/[locale]/landing/AvailablePetSection/AnimalSlider.style.ts @@ -2,16 +2,16 @@ import { cn } from '@gitanimals/ui-tailwind/utils'; export const cardContainer = cn( 'grid gap-5 justify-center grid-cols-[repeat(4,1fr)] grid-rows-[repeat(3,1fr)] w-[1120px] h-[1024px] px-px', - 'max-[1200px]:grid-cols-[repeat(3,1fr)] max-[1200px]:grid-rows-[repeat(3,1fr)] max-[1200px]:w-[835px] max-[1200px]:mx-auto', - 'max-[1200px]:[&>div:nth-child(n+10)]:hidden', - 'max-[900px]:grid-cols-[repeat(2,1fr)] max-[900px]:grid-rows-[repeat(3,1fr)] max-[900px]:w-[530px]', - 'max-[900px]:[&>div:nth-child(n+7)]:hidden', + 'max-1200:grid-cols-[repeat(3,1fr)] max-1200:grid-rows-[repeat(3,1fr)] max-1200:w-[835px] max-1200:mx-auto', + 'max-1200:[&>div:nth-child(n+10)]:hidden', + 'max-900:grid-cols-[repeat(2,1fr)] max-900:grid-rows-[repeat(3,1fr)] max-900:w-[530px]', + 'max-900:[&>div:nth-child(n+7)]:hidden', ); export const cardContainerMobile = cn( 'grid w-[265px] h-[325px] px-px', - 'max-[400px]:w-[200px] max-[400px]:h-[244px]', - 'max-[400px]:[&_.animal-card-info]:bottom-2.5', - 'max-[400px]:[&_.animal-card-type]:font-product max-[400px]:[&_.animal-card-type]:text-glyph-18 max-[400px]:[&_.animal-card-type]:font-bold', - 'max-[400px]:[&_.animal-card-rating]:font-product max-[400px]:[&_.animal-card-rating]:text-glyph-16', + 'max-400:w-[200px] max-400:h-[244px]', + 'max-400:[&_.animal-card-info]:bottom-2.5', + 'max-400:[&_.animal-card-type]:font-product max-400:[&_.animal-card-type]:text-glyph-18 max-400:[&_.animal-card-type]:font-bold', + 'max-400:[&_.animal-card-rating]:font-product max-400:[&_.animal-card-rating]:text-glyph-16', ); diff --git a/apps/web/src/app/[locale]/landing/AvailablePetSection/AnimalSlider.tsx b/apps/web/src/app/[locale]/landing/AvailablePetSection/AnimalSlider.tsx index 9b0e8c22..ff9b3882 100644 --- a/apps/web/src/app/[locale]/landing/AvailablePetSection/AnimalSlider.tsx +++ b/apps/web/src/app/[locale]/landing/AvailablePetSection/AnimalSlider.tsx @@ -3,10 +3,10 @@ import type { PersonaInfo } from '@gitanimals/api'; import { cn } from '@gitanimals/ui-tailwind'; -import { AnimalCard } from '@/components/AnimalCard'; +import { AnimalCard } from '@/entities/persona'; import { MediaQuery } from '@/components/MediaQuery'; import { PerspectiveCenterSlider } from '@/components/Slider'; -import { useGetAllPersona } from '@/hooks/query/render/useGetAllPersona'; +import { useGetAllPersona } from '@/entities/persona/model/useGetAllPersona'; import * as styles from './AnimalSlider.style'; import AnimalSliderContainerMobile from './AnimalSliderContainerMobile'; @@ -68,7 +68,7 @@ export default AnimalSlider; const containerStyle = cn( 'w-[1120px] h-[1024px]', - 'max-[1200px]:w-[835px]', - 'max-[900px]:w-[530px]', - 'max-[600px]:w-[calc(100vw-40px)] max-[600px]:h-[325px] max-[600px]:relative', + 'max-1200:w-[835px]', + 'max-900:w-[530px]', + 'max-600:w-[calc(100vw-40px)] max-600:h-[325px] max-600:relative', ); diff --git a/apps/web/src/app/[locale]/landing/AvailablePetSection/AvailablePetSection.tsx b/apps/web/src/app/[locale]/landing/AvailablePetSection/AvailablePetSection.tsx index d0576cef..71265e68 100644 --- a/apps/web/src/app/[locale]/landing/AvailablePetSection/AvailablePetSection.tsx +++ b/apps/web/src/app/[locale]/landing/AvailablePetSection/AvailablePetSection.tsx @@ -4,10 +4,10 @@ import React, { Suspense } from 'react'; import Image from 'next/image'; import { AnchorButton } from '@gitanimals/ui-tailwind'; -import { useGetTotalProductCount } from '@/hooks/query/auction/useGetTotalProductCount'; -import { useGetTotalIdentityUserCount } from '@/hooks/query/identity/useGetTotalIdentityUserCount'; -import { useGetTotalPersonaCount } from '@/hooks/query/render/useGetTotalPersonaCount'; -import { useGetTotalRenderUserCount } from '@/hooks/query/render/useGetTotalRenderUserCount'; +import { useGetTotalProductCount } from '@/entities/product/model/useGetTotalProductCount'; +import { useGetTotalIdentityUserCount } from '@/entities/user/model/useGetTotalIdentityUserCount'; +import { useGetTotalPersonaCount } from '@/entities/persona/model/useGetTotalPersonaCount'; +import { useGetTotalRenderUserCount } from '@/entities/user/model/useGetTotalRenderUserCount'; import AnimalSlider from './AnimalSlider'; import * as styles from './AvailablePetSection.style'; diff --git a/apps/web/src/app/[locale]/landing/ChoosePetSection/ChoosePetSection.tsx b/apps/web/src/app/[locale]/landing/ChoosePetSection/ChoosePetSection.tsx index 2504e2a9..053d57d3 100644 --- a/apps/web/src/app/[locale]/landing/ChoosePetSection/ChoosePetSection.tsx +++ b/apps/web/src/app/[locale]/landing/ChoosePetSection/ChoosePetSection.tsx @@ -1,8 +1,8 @@ import { Button } from '@gitanimals/ui-tailwind'; -import { getServerAuth } from '@/auth'; +import { getServerAuth } from '@/shared/api/auth'; import { LoginButton } from '@/components/AuthButton'; -import { Link } from '@/i18n/routing'; +import { Link } from '@/shared/i18n/routing'; import * as styles from './ChoosePetSection.style'; diff --git a/apps/web/src/app/[locale]/landing/MainSection/MainSection.tsx b/apps/web/src/app/[locale]/landing/MainSection/MainSection.tsx index ee3282fc..a5455131 100644 --- a/apps/web/src/app/[locale]/landing/MainSection/MainSection.tsx +++ b/apps/web/src/app/[locale]/landing/MainSection/MainSection.tsx @@ -1,9 +1,9 @@ import { Button } from '@gitanimals/ui-tailwind'; -import { getServerAuth } from '@/auth'; +import { getServerAuth } from '@/shared/api/auth'; import { LoginButton } from '@/components/AuthButton'; import { Responsive } from '@/components/Responsive'; -import { Link } from '@/i18n/routing'; +import { Link } from '@/shared/i18n/routing'; import * as styles from './MainSection.style'; import MainSlider from './MainSlider'; diff --git a/apps/web/src/app/[locale]/landing/RankingSection/RankingLink.tsx b/apps/web/src/app/[locale]/landing/RankingSection/RankingLink.tsx index acbf4255..f9df76a2 100644 --- a/apps/web/src/app/[locale]/landing/RankingSection/RankingLink.tsx +++ b/apps/web/src/app/[locale]/landing/RankingSection/RankingLink.tsx @@ -2,7 +2,7 @@ import { useSearchParams } from 'next/navigation'; -import { USER_GITHUB_URL } from '@/constants/route'; +import { USER_GITHUB_URL } from '@/shared/config/route'; export function RankingLink({ children, diff --git a/apps/web/src/app/[locale]/landing/RankingSection/RankingSection.tsx b/apps/web/src/app/[locale]/landing/RankingSection/RankingSection.tsx index 22a219c2..012db925 100644 --- a/apps/web/src/app/[locale]/landing/RankingSection/RankingSection.tsx +++ b/apps/web/src/app/[locale]/landing/RankingSection/RankingSection.tsx @@ -6,7 +6,7 @@ import { cn } from '@gitanimals/ui-tailwind'; import { useQueries } from '@tanstack/react-query'; import { MediaQuery } from '@/components/MediaQuery'; -import { Link } from '@/i18n/routing'; +import { Link } from '@/shared/i18n/routing'; import GameConsole from './GameConsole/GameConsole'; import { MobileGameConsole } from './MobileGameConsole/MobileGameConsole'; diff --git a/apps/web/src/app/[locale]/landing/RankingSection/RankingServerSide.tsx b/apps/web/src/app/[locale]/landing/RankingSection/RankingServerSide.tsx index 401d9bb2..8ffc2fb7 100644 --- a/apps/web/src/app/[locale]/landing/RankingSection/RankingServerSide.tsx +++ b/apps/web/src/app/[locale]/landing/RankingSection/RankingServerSide.tsx @@ -2,7 +2,7 @@ import { getServerSession } from 'next-auth'; import { getRankByUsername } from '@gitanimals/api'; import { rankQueries } from '@gitanimals/react-query'; -import { getDehydratedQueries, Hydrate } from '@/lib/react-query/queryClient'; +import { getDehydratedQueries, Hydrate } from '@/shared/lib/react-query/queryClient'; import { RANKS_PER_PAGE, RANKS_TOP_3 } from './constants'; import RankingSection from './RankingSection'; diff --git a/apps/web/src/app/[locale]/layout.tsx b/apps/web/src/app/[locale]/layout.tsx index d3cd27b8..71966714 100644 --- a/apps/web/src/app/[locale]/layout.tsx +++ b/apps/web/src/app/[locale]/layout.tsx @@ -4,9 +4,9 @@ import { getMessages } from 'next-intl/server'; import { NuqsAdapter } from 'nuqs/adapters/next/app'; import { ClientProvider, GlobalComponent, Monitoring } from '@/components/Global'; -import { config } from '@/constants/config'; -import type { Locale } from '@/i18n/routing'; -import { LOCALE_LIST } from '@/i18n/routing'; +import { config } from '@/shared/config/config'; +import type { Locale } from '@/shared/i18n/routing'; +import { LOCALE_LIST } from '@/shared/i18n/routing'; import '@egjs/react-flicking/dist/flicking.css'; import '@egjs/react-flicking/dist/flicking-inline.css'; diff --git a/apps/web/src/app/[locale]/mypage/(github-custom)/FarmBackgroundSelect.tsx b/apps/web/src/app/[locale]/mypage/(github-custom)/FarmBackgroundSelect.tsx index 8db4deec..cce930b3 100644 --- a/apps/web/src/app/[locale]/mypage/(github-custom)/FarmBackgroundSelect.tsx +++ b/apps/web/src/app/[locale]/mypage/(github-custom)/FarmBackgroundSelect.tsx @@ -6,9 +6,9 @@ import { cn } from '@gitanimals/ui-tailwind/utils'; import { wrap } from '@suspensive/react'; import { useSuspenseQuery } from '@tanstack/react-query'; -import { customScrollStyle } from '@/styles/scrollStyle'; -import { useClientSession, useClientUser } from '@/utils/clientAuth'; -import { getBackgroundImage } from '@/utils/image'; +import { customScrollStyle } from '@/shared/styles/scrollStyle'; +import { useClientSession, useClientUser } from '@/shared/utils/clientAuth'; +import { getBackgroundImage } from '@/shared/utils/image'; export const FarmBackgroundSelect = wrap .ErrorBoundary({ fallback: <> }) diff --git a/apps/web/src/app/[locale]/mypage/(github-custom)/FarmPersonaSelect.tsx b/apps/web/src/app/[locale]/mypage/(github-custom)/FarmPersonaSelect.tsx index e0121264..acc4823c 100644 --- a/apps/web/src/app/[locale]/mypage/(github-custom)/FarmPersonaSelect.tsx +++ b/apps/web/src/app/[locale]/mypage/(github-custom)/FarmPersonaSelect.tsx @@ -10,7 +10,7 @@ import { useQueryClient } from '@tanstack/react-query'; import { ExpandIcon } from 'lucide-react'; import { toast } from 'sonner'; -import { useChangePersonaVisible } from '@/apis/persona/useChangePersonaVisible'; +import { useChangePersonaVisible } from '@/features/change-persona-visible/model/useChangePersonaVisible'; import { SelectPersonaList } from '../PersonaList'; diff --git a/apps/web/src/app/[locale]/mypage/(github-custom)/FarmType.tsx b/apps/web/src/app/[locale]/mypage/(github-custom)/FarmType.tsx index c82eda31..fc1d3bdc 100644 --- a/apps/web/src/app/[locale]/mypage/(github-custom)/FarmType.tsx +++ b/apps/web/src/app/[locale]/mypage/(github-custom)/FarmType.tsx @@ -6,9 +6,9 @@ import { cn } from '@gitanimals/ui-tailwind'; import { ClipboardIcon } from 'lucide-react'; import { toast } from 'sonner'; -import { getGitanimalsFarmString, GitanimalsFarm } from '@/components/Gitanimals'; -import { useClientUser } from '@/utils/clientAuth'; -import { copyClipBoard } from '@/utils/copy'; +import { getGitanimalsFarmString, GitanimalsFarm } from '@/shared/ui/Gitanimals'; +import { useClientUser } from '@/shared/utils/clientAuth'; +import { copyClipBoard } from '@/shared/utils/copy'; import { FarmBackgroundSelect } from './FarmBackgroundSelect'; import { FarmPersonaSelect } from './FarmPersonaSelect'; diff --git a/apps/web/src/app/[locale]/mypage/(github-custom)/LinePreview.tsx b/apps/web/src/app/[locale]/mypage/(github-custom)/LinePreview.tsx index 41c10f1d..cf695ecc 100644 --- a/apps/web/src/app/[locale]/mypage/(github-custom)/LinePreview.tsx +++ b/apps/web/src/app/[locale]/mypage/(github-custom)/LinePreview.tsx @@ -7,10 +7,10 @@ import { Button, TextField } from '@gitanimals/ui-tailwind'; import { ClipboardIcon } from 'lucide-react'; import { toast } from 'sonner'; -import { getGitanimalsLineString, GitanimalsLine } from '@/components/Gitanimals'; -import { customScrollStyle } from '@/styles/scrollStyle'; -import { useClientUser } from '@/utils/clientAuth'; -import { copyClipBoard } from '@/utils/copy'; +import { getGitanimalsLineString, GitanimalsLine } from '@/shared/ui/Gitanimals'; +import { customScrollStyle } from '@/shared/styles/scrollStyle'; +import { useClientUser } from '@/shared/utils/clientAuth'; +import { copyClipBoard } from '@/shared/utils/copy'; const DEFAULT_SIZE = { width: 600, height: 120 }; diff --git a/apps/web/src/app/[locale]/mypage/PersonaList.tsx b/apps/web/src/app/[locale]/mypage/PersonaList.tsx index 6a03ee7c..53648dc6 100644 --- a/apps/web/src/app/[locale]/mypage/PersonaList.tsx +++ b/apps/web/src/app/[locale]/mypage/PersonaList.tsx @@ -8,11 +8,11 @@ import { cn, Skeleton } from '@gitanimals/ui-tailwind'; import { wrap } from '@suspensive/react'; import { useSuspenseQuery } from '@tanstack/react-query'; -import { MemoizedBannerPersonaItem } from '@/components/PersonaItem'; -import { PersonaListToolbar } from '@/components/PersonaListToolbar'; -import type { PersonaFilterState } from '@/hooks/persona/usePersonaListFilter'; -import { usePersonaListFilter } from '@/hooks/persona/usePersonaListFilter'; -import { useClientUser } from '@/utils/clientAuth'; +import { MemoizedBannerPersonaItem } from '@/entities/persona/ui/PersonaItem'; +import { PersonaListToolbar } from '@/entities/persona/ui/PersonaListToolbar'; +import type { PersonaFilterState } from '@/entities/persona/model/usePersonaListFilter'; +import { usePersonaListFilter } from '@/entities/persona/model/usePersonaListFilter'; +import { useClientUser } from '@/shared/utils/clientAuth'; // ─── Context ──────────────────────────────────────────────────────── diff --git a/apps/web/src/app/[locale]/mypage/ProfileSection.tsx b/apps/web/src/app/[locale]/mypage/ProfileSection.tsx index 7b6d712a..01c2ffb0 100644 --- a/apps/web/src/app/[locale]/mypage/ProfileSection.tsx +++ b/apps/web/src/app/[locale]/mypage/ProfileSection.tsx @@ -11,8 +11,8 @@ import { wrap } from '@suspensive/react'; import { useSuspenseQuery } from '@tanstack/react-query'; import { ChevronRight, FlaskConical } from 'lucide-react'; -import { Link, usePathname } from '@/i18n/routing'; -import { addNumberComma } from '@/utils/number'; +import { Link, usePathname } from '@/shared/i18n/routing'; +import { addNumberComma } from '@/shared/utils/number'; export const ProfileSection = memo( wrap @@ -47,7 +47,7 @@ export const ProfileSection = memo(

diff --git a/apps/web/src/app/[locale]/mypage/layout.tsx b/apps/web/src/app/[locale]/mypage/layout.tsx index e95d195f..dbde086b 100644 --- a/apps/web/src/app/[locale]/mypage/layout.tsx +++ b/apps/web/src/app/[locale]/mypage/layout.tsx @@ -1,6 +1,6 @@ import { cn } from '@gitanimals/ui-tailwind/utils'; -import GNB from '@/components/GNB/GNB'; +import GNB from '@/widgets/gnb/GNB'; import { ProfileSection } from './ProfileSection'; diff --git a/apps/web/src/app/[locale]/mypage/my-pet/(evolution)/EvolutionPersona.tsx b/apps/web/src/app/[locale]/mypage/my-pet/(evolution)/EvolutionPersona.tsx index 5006ecac..c1230ac1 100644 --- a/apps/web/src/app/[locale]/mypage/my-pet/(evolution)/EvolutionPersona.tsx +++ b/apps/web/src/app/[locale]/mypage/my-pet/(evolution)/EvolutionPersona.tsx @@ -1,8 +1,7 @@ 'use client'; -import { useState } from 'react'; import { useTranslations } from 'next-intl'; -import { evolutionPersona, type MergePersonaLevelResponse, type Persona } from '@gitanimals/api'; +import { evolutionPersona, type Persona } from '@gitanimals/api'; import { userQueries } from '@gitanimals/react-query'; import { Button, CommonDialog } from '@gitanimals/ui-tailwind'; import { useMutation, useQueryClient } from '@tanstack/react-query'; @@ -11,7 +10,6 @@ import { overlay } from 'overlay-kit'; import { PersonaBanner, PersonaBannerUnknown, PersonaGradientBanner } from '../_components/PersonaBanner'; import { SpinningLoader } from '../_components/SpinningLoader'; -import { MergeResultModal } from '../(merge)/MergeResult'; import { EvolutionResult } from './EvolutionResult'; @@ -25,13 +23,9 @@ export function EvolutionPersona({ isOpen, onClose, targetPersona }: EvolutionPe const queryClient = useQueryClient(); const t = useTranslations('Mypage'); - const [resultData, setResultData] = useState(null); - const { mutate, isPending: isEvolving } = useMutation({ mutationFn: () => evolutionPersona(targetPersona.id), onSuccess: (data) => { - console.log('data', data); - overlay.open(({ isOpen, close }) => ( close()} result={data} /> )); @@ -52,12 +46,6 @@ export function EvolutionPersona({ isOpen, onClose, targetPersona }: EvolutionPe

- setResultData(null)} - result={resultData as MergePersonaLevelResponse} - /> {isEvolving && } ); diff --git a/apps/web/src/app/[locale]/mypage/my-pet/(evolution)/EvolutionResult.tsx b/apps/web/src/app/[locale]/mypage/my-pet/(evolution)/EvolutionResult.tsx index 17b882af..c604d10e 100644 --- a/apps/web/src/app/[locale]/mypage/my-pet/(evolution)/EvolutionResult.tsx +++ b/apps/web/src/app/[locale]/mypage/my-pet/(evolution)/EvolutionResult.tsx @@ -9,7 +9,7 @@ import { snakeToTitleCase } from '@gitanimals/util-common'; import { AnimatePresence, motion } from 'framer-motion'; import { X } from 'lucide-react'; -import { getPersonaImage } from '@/utils/image'; +import { getPersonaImage } from '@/shared/utils/image'; interface MergeResultModalProps { isOpen: boolean; diff --git a/apps/web/src/app/[locale]/mypage/my-pet/(merge)/MergePersona.tsx b/apps/web/src/app/[locale]/mypage/my-pet/(merge)/MergePersona.tsx index 6cd49e5e..14d6eee6 100644 --- a/apps/web/src/app/[locale]/mypage/my-pet/(merge)/MergePersona.tsx +++ b/apps/web/src/app/[locale]/mypage/my-pet/(merge)/MergePersona.tsx @@ -7,7 +7,7 @@ import { useMergePersonaLevelByToken, userQueries } from '@gitanimals/react-quer import { Button, CommonDialog, Dialog } from '@gitanimals/ui-tailwind'; import { useQueryClient } from '@tanstack/react-query'; -import { useClientSession } from '@/utils/clientAuth'; +import { useClientSession } from '@/shared/utils/clientAuth'; import { SelectPersonaList } from '../_components/SelectPersonaList'; import { SpinningLoader } from '../_components/SpinningLoader'; diff --git a/apps/web/src/app/[locale]/mypage/my-pet/(merge)/MergeResult.tsx b/apps/web/src/app/[locale]/mypage/my-pet/(merge)/MergeResult.tsx index e77cae5d..a99aee56 100644 --- a/apps/web/src/app/[locale]/mypage/my-pet/(merge)/MergeResult.tsx +++ b/apps/web/src/app/[locale]/mypage/my-pet/(merge)/MergeResult.tsx @@ -9,7 +9,7 @@ import { snakeToTitleCase } from '@gitanimals/util-common'; import { AnimatePresence, motion } from 'framer-motion'; import { X } from 'lucide-react'; -import { getPersonaImage } from '@/utils/image'; +import { getPersonaImage } from '@/shared/utils/image'; interface MergeResultModalProps { isOpen: boolean; diff --git a/apps/web/src/app/[locale]/mypage/my-pet/BEHAVIORS.md b/apps/web/src/app/[locale]/mypage/my-pet/BEHAVIORS.md new file mode 100644 index 00000000..7ecdadc6 --- /dev/null +++ b/apps/web/src/app/[locale]/mypage/my-pet/BEHAVIORS.md @@ -0,0 +1,88 @@ +# my-pet 페이지에서 일어날 수 있는 동작 + +대상: [`page.tsx`](./page.tsx)가 렌더하는 트리 (`SelectedPetTable`, [`../PersonaList.tsx`의 `SelectPersonaList`](../PersonaList.tsx)). + +```mermaid +flowchart TB + subgraph page [my-pet page] + table[SelectedPetTable] + list[SelectPersonaList Toolbar + Grid] + end + table --> sellDlg[SellConfirmDialog Dialog] + table --> mergeOv[overlay MergePersona CommonDialog] + table --> evoOv[overlay EvolutionPersona CommonDialog] + mergeOv --> mergeRes[MergeResultModal motion overlay] + mergeOv --> mergeList[SelectPersonaList in _components] + evoOv --> evoRes[overlay EvolutionResult] + list --> toolbar[PersonaListToolbar] + list --> gridClick[펫 카드 클릭 선택] +``` + +--- + +## 1. 페이지 본문 (`page.tsx`) + +| 동작 | 설명 | +| --------- | ---------------------------------------------------------------------------------------------- | +| 초기 선택 | `initSelectPersonas`: 페르소나 목록이 로드되면 아직 선택이 없을 때 **첫 번째 펫을 자동 선택**. | +| 스크롤 | 펫 그리드 영역은 `ScrollArea`로 **세로 스크롤**. | +| 하단 문구 | `sell-to-other` 번역 문자열이 **5초 후 fade-in** (클릭 동작 없음). | + +--- + +## 2. 상단 행 (`SelectedPetTable.tsx`) + +**전제:** `currentPersona`가 있을 때만 펫 정보·버튼이 보입니다. + +| 동작 | UI / 메커니즘 | 결과 | +| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ | +| **판매 (100P)** | 로컬 스토리지 `LOCAL_STORAGE_KEY.isDoNotShowAgain`이 true면 확인 없이 `dropPet` API; 아니면 **`Dialog`** (`SellConfirmDialog`) 열림. 확인 시 다시 `dropPet`, 체크 시 “다시 보지 않기” 저장. | 성공 시 `toast.success`, `userQueries` 무효화, 판매 대상 id 초기화, `reset()`으로 상단 선택 해제. 실패 시 `toast.error`. | +| **합성(merge)** | `overlay-kit`으로 [`MergePersona`](./(merge)/MergePersona.tsx) (`CommonDialog`, large). | 아래 3절 참고. | +| **진화(evolution)** | `currentPersona.isEvolutionable`일 때만 버튼 표시. `overlay-kit`으로 [`EvolutionPersona`](./(evolution)/EvolutionPersona.tsx) (`CommonDialog`, large). | 아래 4절 참고. | + +**판매 확인 모달:** 제목/설명, “다시 보지 않기” `Checkbox`, 닫기/확인 버튼, `onOpenChange`로 닫기. + +--- + +## 3. 합성 플로우 (`MergePersona`) + +| 동작 | 설명 | +| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 다이얼로그 닫기 | 푸터 **취소** 또는 `CommonDialog`의 닫기 동작. | +| 재료 펫 선택 | 내부 [`_components/SelectPersonaList`](./_components/SelectPersonaList.tsx): 검색·등급·티어·정렬·필터 초기화(`PersonaListToolbar`). 펫 클릭 시 타겟과 같으면 재료 해제, 다르면 재료로 설정. | +| 합성 실행 | 타겟+재료 모두 있을 때만 활성화. `useMergePersonaLevelByToken` mutation. 진행 중 **`SpinningLoader`** 표시. | +| 성공 후 | `MergeResultModal`: 커스텀 **전체 화면 dim + 중앙 카드(framer-motion)**, 배경/X로 닫기. `userQueries.allPersonasKey` 무효화, 결과로 타겟 페르소나 상태 갱신. | + +--- + +## 4. 진화 플로우 (`EvolutionPersona`) + +| 동작 | 설명 | +| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 다이얼로그 | 프리뷰(`EvolutionPreview`) + **진화** 버튼 한 번. | +| 진행 중 | `SpinningLoader`. | +| 성공 시 | `evolutionPersona` mutation `onSuccess`: **`overlay.open(EvolutionResult)`** (별도 결과 오버레이), **부모 `CommonDialog`는 `onClose()`**, 페르소나 쿼리 무효화. | + +--- + +## 5. 하단 펫 목록 (`SelectPersonaList` in `PersonaList.tsx`) + +| 동작 | 설명 | +| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **툴바** | `PersonaListToolbar` (`@/components/PersonaListToolbar`): 검색(`SearchBar`), 등급·티어 칩, **진화 가능만** 토글(`showEvolvableFilter`), 정렬, 조건 있을 때 **초기화(`RotateCcw`)**. | +| **그리드** | `MemoizedBannerPersonaItem` (`@/components/PersonaItem`) 클릭 → `onSelectPersona` → 페이지 state **`selectPersona` 갱신** (상단 테이블에 반영). `isSpecialEffect`로 표시 옵션. | +| **빈 목록** | 필터 결과 0건 시 `Mypage.Filter.no-results` 문구. | +| **로딩/에러** | Suspense 스켈레톤; ErrorBoundary 시 `error` 텍스트. | + +데이터: `useSuspenseQuery(userQueries.allPersonasOptions(name))` — 로그인 사용자 이름 기준 전체 페르소나. + +--- + +## 6. 요약: 모달/오버레이 + +1. **Radix `Dialog`**: 판매 확인 (`SellConfirmDialog`). +2. **`CommonDialog` (overlay-kit)**: 합성(`MergePersona`), 진화(`EvolutionPersona`). +3. **`overlay-kit` 추가 레이어**: 진화 성공 시 `EvolutionResult`. +4. **`MergeResultModal`**: 합성 성공 후 커스텀 dim 레이어. + +이 문서는 해당 페이지에서 코드 기준으로 일어날 수 있는 사용자 동작을 정리한 것입니다. diff --git a/apps/web/src/app/[locale]/mypage/my-pet/SelectedPetTable.tsx b/apps/web/src/app/[locale]/mypage/my-pet/SelectedPetTable.tsx index 78d389b8..40c8864f 100644 --- a/apps/web/src/app/[locale]/mypage/my-pet/SelectedPetTable.tsx +++ b/apps/web/src/app/[locale]/mypage/my-pet/SelectedPetTable.tsx @@ -5,15 +5,15 @@ import Image from 'next/image'; import { useTranslations } from 'next-intl'; import { dropPet, type Persona } from '@gitanimals/api'; import { userQueries } from '@gitanimals/react-query'; -import { Button, Checkbox, Dialog, Label } from '@gitanimals/ui-tailwind'; +import { Button, Checkbox, ConfirmDialog, Label } from '@gitanimals/ui-tailwind'; import { snakeToTitleCase } from '@gitanimals/util-common'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { overlay } from 'overlay-kit'; import { toast } from 'sonner'; -import { LOCAL_STORAGE_KEY } from '@/constants/storage'; -import { ANIMAL_TIER_TEXT_MAP, getAnimalTierInfo } from '@/utils/animals'; -import { getPersonaImage } from '@/utils/image'; +import { LOCAL_STORAGE_KEY } from '@/shared/config/storage'; +import { ANIMAL_TIER_TEXT_MAP, getAnimalTierInfo } from '@/shared/utils/animals'; +import { getPersonaImage } from '@/shared/utils/image'; import { EvolutionPersona } from './(evolution)'; import { MergePersona } from './(merge)'; @@ -157,42 +157,25 @@ function SellConfirmDialog({ onClose: () => void; isOpen: boolean; }) { - const [isLoading, setIsLoading] = useState(false); const t = useTranslations(); const [isDoNotShowAgain, setIsDoNotShowAgain] = useState(false); - const confirmDialog = async () => { - if (isLoading) return; - - setIsLoading(true); - await onConfirm(isDoNotShowAgain); - setIsLoading(false); - }; - return ( - - - {t('Shop.sell-confirm')} - -

{t('Shop.sell-confirm-description')}

-
-
-
- setIsDoNotShowAgain(!isDoNotShowAgain)} /> - -
-
- - -
-
-
-
+ onConfirm(isDoNotShowAgain)} + title={t('Shop.sell-confirm')} + description={t('Shop.sell-confirm-description')} + confirmText={t('Common.confirm')} + cancelText={t('Common.close')} + > +
+ setIsDoNotShowAgain(!isDoNotShowAgain)} /> + +
+
); } diff --git a/apps/web/src/app/[locale]/mypage/my-pet/_components/PersonaBanner.tsx b/apps/web/src/app/[locale]/mypage/my-pet/_components/PersonaBanner.tsx index 78b58ed1..32f969f7 100644 --- a/apps/web/src/app/[locale]/mypage/my-pet/_components/PersonaBanner.tsx +++ b/apps/web/src/app/[locale]/mypage/my-pet/_components/PersonaBanner.tsx @@ -1,6 +1,6 @@ import { cn } from '@gitanimals/ui-tailwind/utils'; -import { getPersonaImage } from '@/utils/image'; +import { getPersonaImage } from '@/shared/utils/image'; export function PersonaBanner({ level, personaType }: { level: number | string; personaType: string }) { return ( diff --git a/apps/web/src/app/[locale]/mypage/my-pet/_components/SelectPersonaList.tsx b/apps/web/src/app/[locale]/mypage/my-pet/_components/SelectPersonaList.tsx index c3ab4917..da1df9d3 100644 --- a/apps/web/src/app/[locale]/mypage/my-pet/_components/SelectPersonaList.tsx +++ b/apps/web/src/app/[locale]/mypage/my-pet/_components/SelectPersonaList.tsx @@ -5,11 +5,11 @@ import { cn, Skeleton } from '@gitanimals/ui-tailwind'; import { wrap } from '@suspensive/react'; import { useSuspenseQuery } from '@tanstack/react-query'; -import { MemoizedLevelPersonaItem } from '@/components/PersonaItem'; -import { PersonaListToolbar } from '@/components/PersonaListToolbar'; -import { usePersonaListFilter } from '@/hooks/persona/usePersonaListFilter'; -import { customScrollStyle } from '@/styles/scrollStyle'; -import { useClientUser } from '@/utils/clientAuth'; +import { MemoizedLevelPersonaItem } from '@/entities/persona/ui/PersonaItem'; +import { PersonaListToolbar } from '@/entities/persona/ui/PersonaListToolbar'; +import { usePersonaListFilter } from '@/entities/persona/model/usePersonaListFilter'; +import { customScrollStyle } from '@/shared/styles/scrollStyle'; +import { useClientUser } from '@/shared/utils/clientAuth'; function BannerSkeletonList({ length }: { length: number }) { return ( diff --git a/apps/web/src/app/[locale]/mypage/page.tsx b/apps/web/src/app/[locale]/mypage/page.tsx index b2707458..0ddd3236 100644 --- a/apps/web/src/app/[locale]/mypage/page.tsx +++ b/apps/web/src/app/[locale]/mypage/page.tsx @@ -3,7 +3,7 @@ import type { Metadata } from 'next'; import { getTranslations } from 'next-intl/server'; import { cn } from '@gitanimals/ui-tailwind/utils'; -import { Link } from '@/i18n/routing'; +import { Link } from '@/shared/i18n/routing'; import { FarmType } from './(github-custom)/FarmType'; import { LineType } from './(github-custom)/LineType'; diff --git a/apps/web/src/app/[locale]/not-found.tsx b/apps/web/src/app/[locale]/not-found.tsx index a244c8df..167e5e23 100644 --- a/apps/web/src/app/[locale]/not-found.tsx +++ b/apps/web/src/app/[locale]/not-found.tsx @@ -3,11 +3,11 @@ import { useEffect } from 'react'; import { useTranslations } from 'next-intl'; -import { sendMessageToErrorChannel } from '@/apis/slack/sendMessage'; +import { sendMessageToErrorChannel } from '@/shared/api/slack'; import { ErrorPage } from '@/components/Error/ErrorPage'; -import { isDev } from '@/constants/env'; -import { GITHUB_ISSUE_URL } from '@/constants/outlink'; -import { usePathname, useRouter } from '@/i18n/routing'; +import { isDev } from '@/shared/config/env'; +import { GITHUB_ISSUE_URL } from '@/shared/config/outlink'; +import { usePathname, useRouter } from '@/shared/i18n/routing'; export default function NotFound() { const pathname = usePathname(); diff --git a/apps/web/src/app/[locale]/page.tsx b/apps/web/src/app/[locale]/page.tsx index 0de57659..9f2e94f9 100644 --- a/apps/web/src/app/[locale]/page.tsx +++ b/apps/web/src/app/[locale]/page.tsx @@ -3,7 +3,7 @@ import { getTranslations } from 'next-intl/server'; import { ErrorBoundary } from '@suspensive/react'; import { ErrorSection } from '@/components/Error/ErrorSection'; -import GNB from '@/components/GNB/GNB'; +import GNB from '@/widgets/gnb/GNB'; import { SpringContent } from './landing/(spring)'; import { diff --git a/apps/web/src/app/[locale]/shop/_auction/PersonaSearch.tsx b/apps/web/src/app/[locale]/shop/_auction/PersonaSearch.tsx index c3bde7a3..6213e7a6 100644 --- a/apps/web/src/app/[locale]/shop/_auction/PersonaSearch.tsx +++ b/apps/web/src/app/[locale]/shop/_auction/PersonaSearch.tsx @@ -9,8 +9,8 @@ import { wrap } from '@suspensive/react'; import { useSuspenseQuery } from '@tanstack/react-query'; import { LoaderIcon, SearchIcon, XIcon } from 'lucide-react'; -import { customScrollStyle } from '@/styles/scrollStyle'; -import { getPersonaImage } from '@/utils/image'; +import { customScrollStyle } from '@/shared/styles/scrollStyle'; +import { getPersonaImage } from '@/shared/utils/image'; const EVENT = { CHRISTMAS: { diff --git a/apps/web/src/app/[locale]/shop/_auction/ProductTable.tsx b/apps/web/src/app/[locale]/shop/_auction/ProductTable.tsx index 764df7a4..7485e95e 100644 --- a/apps/web/src/app/[locale]/shop/_auction/ProductTable.tsx +++ b/apps/web/src/app/[locale]/shop/_auction/ProductTable.tsx @@ -12,8 +12,8 @@ import { toast } from 'sonner'; import { MediaQuery } from '@/components/MediaQuery'; import Pagination from '@/components/Pagination/Pagination'; -import { useLoading } from '@/store/loading'; -import { useClientUser } from '@/utils/clientAuth'; +import { useLoading } from '@/shared/store/loading'; +import { useClientUser } from '@/shared/utils/clientAuth'; import { ShopTableDesktopRow, ShopTableMobileRow, ShopTableRowViewSkeleton } from '../_common/ShopTableMobileRow'; import { useSearchOptions } from '../useSearchOptions'; diff --git a/apps/web/src/app/[locale]/shop/_auction/SellSection/PetList.tsx b/apps/web/src/app/[locale]/shop/_auction/SellSection/PetList.tsx index 6ea17e75..5137e948 100644 --- a/apps/web/src/app/[locale]/shop/_auction/SellSection/PetList.tsx +++ b/apps/web/src/app/[locale]/shop/_auction/SellSection/PetList.tsx @@ -5,9 +5,9 @@ import { userQueries } from '@gitanimals/react-query'; import { cn } from '@gitanimals/ui-tailwind/utils'; import { useQuery } from '@tanstack/react-query'; -import { customScrollStyle } from '@/styles/scrollStyle'; -import { useClientUser } from '@/utils/clientAuth'; -import { getPersonaImage } from '@/utils/image'; +import { customScrollStyle } from '@/shared/styles/scrollStyle'; +import { useClientUser } from '@/shared/utils/clientAuth'; +import { getPersonaImage } from '@/shared/utils/image'; interface Props { selectedPersona?: Persona | null; diff --git a/apps/web/src/app/[locale]/shop/_auction/SellSection/SellInputRow.tsx b/apps/web/src/app/[locale]/shop/_auction/SellSection/SellInputRow.tsx index 412932af..1f44f250 100644 --- a/apps/web/src/app/[locale]/shop/_auction/SellSection/SellInputRow.tsx +++ b/apps/web/src/app/[locale]/shop/_auction/SellSection/SellInputRow.tsx @@ -11,10 +11,10 @@ import { snakeToTitleCase } from '@gitanimals/util-common'; import { useQueryClient } from '@tanstack/react-query'; import { toast } from 'sonner'; -import { useRegisterProduct } from '@/apis/auctions/useRegisterProduct'; -import { useGetPersonaTier } from '@/hooks/persona/useGetPersonaDropRate'; -import { ANIMAL_TIER_TEXT_MAP } from '@/utils/animals'; -import { getPersonaImage } from '@/utils/image'; +import { useRegisterProduct } from '@/features/auction/model/useRegisterProduct'; +import { useGetPersonaTier } from '@/entities/persona/model/useGetPersonaDropRate'; +import { ANIMAL_TIER_TEXT_MAP } from '@/shared/utils/animals'; +import { getPersonaImage } from '@/shared/utils/image'; import { rowStyle, ShopTableMobileRow } from '../../_common/ShopTableMobileRow'; import { tableCss, theadCss } from '../table.styles'; diff --git a/apps/web/src/app/[locale]/shop/_auction/Tab.tsx b/apps/web/src/app/[locale]/shop/_auction/Tab.tsx index 2ec9fd2f..52ee11eb 100644 --- a/apps/web/src/app/[locale]/shop/_auction/Tab.tsx +++ b/apps/web/src/app/[locale]/shop/_auction/Tab.tsx @@ -1,7 +1,7 @@ import type { ReactNode } from 'react'; import { cn } from '@gitanimals/ui-tailwind/utils'; -import { Link } from '@/i18n/routing'; +import { Link } from '@/shared/i18n/routing'; import type { TabType } from '../type'; @@ -48,7 +48,7 @@ function Tab({ selectedTab, rightElement }: Props) { 'max-mobile:flex-col max-mobile:items-start max-mobile:gap-8 max-mobile:mb-3', )} > -
+
{TAB.map((item) => ( ))} diff --git a/apps/web/src/app/[locale]/shop/_auction/index.tsx b/apps/web/src/app/[locale]/shop/_auction/index.tsx index bb95defa..60798b08 100644 --- a/apps/web/src/app/[locale]/shop/_auction/index.tsx +++ b/apps/web/src/app/[locale]/shop/_auction/index.tsx @@ -53,7 +53,7 @@ export function AuctionSection({ selectedTab }: Props) { 'rounded-2xl bg-white/10 backdrop-blur-[7px]', 'p-10 max-w-[1120px] w-full z-floating', 'min-h-[924px] h-auto', - 'max-mobile:h-auto max-mobile:bg-transparent max-mobile:backdrop-blur-0', + 'max-mobile:min-h-[auto] max-mobile:h-auto max-mobile:bg-transparent max-mobile:backdrop-blur-0', 'max-mobile:p-0 max-mobile:pb-3', )} > diff --git a/apps/web/src/app/[locale]/shop/_background/BackgroundSection.tsx b/apps/web/src/app/[locale]/shop/_background/BackgroundSection.tsx index 80084c64..367e127f 100644 --- a/apps/web/src/app/[locale]/shop/_background/BackgroundSection.tsx +++ b/apps/web/src/app/[locale]/shop/_background/BackgroundSection.tsx @@ -13,11 +13,11 @@ import { AxiosError } from 'axios'; import { toast } from 'sonner'; import EmblaCarousel from '@/components/EmblaCarousel'; -import { trackEvent } from '@/lib/analytics'; -import { getQueryClient } from '@/lib/react-query/queryClient'; -import { useClientUser } from '@/utils/clientAuth'; -import { getBackgroundImage } from '@/utils/image'; -import { addNumberComma } from '@/utils/number'; +import { trackEvent } from '@/shared/lib/analytics'; +import { getQueryClient } from '@/shared/lib/react-query/queryClient'; +import { useClientUser } from '@/shared/utils/clientAuth'; +import { getBackgroundImage } from '@/shared/utils/image'; +import { addNumberComma } from '@/shared/utils/number'; export const BackgroundSection = wrap .ErrorBoundary({ diff --git a/apps/web/src/app/[locale]/shop/_common/FloatingPointSection.tsx b/apps/web/src/app/[locale]/shop/_common/FloatingPointSection.tsx index c993b916..30da8cff 100644 --- a/apps/web/src/app/[locale]/shop/_common/FloatingPointSection.tsx +++ b/apps/web/src/app/[locale]/shop/_common/FloatingPointSection.tsx @@ -7,7 +7,7 @@ import { cn } from '@gitanimals/ui-tailwind'; import { wrap } from '@suspensive/react'; import { useSuspenseQuery } from '@tanstack/react-query'; -import { addNumberComma } from '@/utils/number'; +import { addNumberComma } from '@/shared/utils/number'; export const FloatingPointSection = memo( wrap diff --git a/apps/web/src/app/[locale]/shop/_common/ShopTableMobileRow.tsx b/apps/web/src/app/[locale]/shop/_common/ShopTableMobileRow.tsx index 5128d14a..54c19f47 100644 --- a/apps/web/src/app/[locale]/shop/_common/ShopTableMobileRow.tsx +++ b/apps/web/src/app/[locale]/shop/_common/ShopTableMobileRow.tsx @@ -4,10 +4,10 @@ import type { Product } from '@gitanimals/api'; import { cn } from '@gitanimals/ui-tailwind/utils'; import { snakeToTitleCase } from '@gitanimals/util-common'; -import { useGetPersonaTier } from '@/hooks/persona/useGetPersonaDropRate'; -import { ANIMAL_TIER_TEXT_MAP } from '@/utils/animals'; -import { getPersonaImage } from '@/utils/image'; -import { addNumberComma } from '@/utils/number'; +import { useGetPersonaTier } from '@/entities/persona/model/useGetPersonaDropRate'; +import { ANIMAL_TIER_TEXT_MAP } from '@/shared/utils/animals'; +import { getPersonaImage } from '@/shared/utils/image'; +import { addNumberComma } from '@/shared/utils/number'; interface Props { rightElement: ReactNode; diff --git a/apps/web/src/app/[locale]/shop/_petGotcha/CardFlipGame.tsx b/apps/web/src/app/[locale]/shop/_petGotcha/CardFlipGame.tsx index 5582c2c2..e9d51478 100644 --- a/apps/web/src/app/[locale]/shop/_petGotcha/CardFlipGame.tsx +++ b/apps/web/src/app/[locale]/shop/_petGotcha/CardFlipGame.tsx @@ -4,9 +4,9 @@ import React, { useCallback, useEffect, useState } from 'react'; import { cn } from '@gitanimals/ui-tailwind'; import { GameCard } from '@gitanimals/ui-tailwind'; -import { AnimalCardBack } from '@/components/AnimalCard/AnimalCard'; -import type { AnimalTierType } from '@/components/AnimalCard/AnimalCard.constant'; -import { getPersonaImage } from '@/utils/image'; +import { AnimalCardBack } from '@/entities/persona'; +import type { AnimalTierType } from '@/shared/config/animalTier'; +import { getPersonaImage } from '@/shared/utils/image'; interface CardFlipGameProps { getPersona: { diff --git a/apps/web/src/app/[locale]/shop/_petGotcha/OnePet.tsx b/apps/web/src/app/[locale]/shop/_petGotcha/OnePet.tsx index 841088c5..f42f9695 100644 --- a/apps/web/src/app/[locale]/shop/_petGotcha/OnePet.tsx +++ b/apps/web/src/app/[locale]/shop/_petGotcha/OnePet.tsx @@ -11,11 +11,11 @@ import { useQueryClient } from '@tanstack/react-query'; import { overlay } from 'overlay-kit'; import { toast } from 'sonner'; -import { sendMessageToErrorChannel } from '@/apis/slack/sendMessage'; +import { sendMessageToErrorChannel } from '@/shared/api/slack'; import { SelectedCardMotion } from '@/components/CardGame/FanDrawingGame/CardMotion'; import { CardDrawingGame, DetailedCard } from '@/components/CardGame/FanDrawingGame/FanDrawingGame'; -import { GITHUB_ISSUE_URL } from '@/constants/outlink'; -import { trackEvent } from '@/lib/analytics'; +import { GITHUB_ISSUE_URL } from '@/shared/config/outlink'; +import { trackEvent } from '@/shared/lib/analytics'; import { useCheckEnoughMoney } from './useCheckEnoughMoney'; diff --git a/apps/web/src/app/[locale]/shop/_petGotcha/TenCardFlipGame.tsx b/apps/web/src/app/[locale]/shop/_petGotcha/TenCardFlipGame.tsx index cf0205c7..0c0fa36d 100644 --- a/apps/web/src/app/[locale]/shop/_petGotcha/TenCardFlipGame.tsx +++ b/apps/web/src/app/[locale]/shop/_petGotcha/TenCardFlipGame.tsx @@ -3,8 +3,8 @@ import type { GotchaResult } from '@gitanimals/api'; import { cn } from '@gitanimals/ui-tailwind/utils'; import { motion } from 'framer-motion'; -import { AnimalCard } from '@/components/AnimalCard'; -import { AnimalCardBack } from '@/components/AnimalCard/AnimalCard'; +import { AnimalCard } from '@/entities/persona'; +import { AnimalCardBack } from '@/entities/persona'; const Card = ({ onClick, diff --git a/apps/web/src/app/[locale]/shop/_petGotcha/TenPet.tsx b/apps/web/src/app/[locale]/shop/_petGotcha/TenPet.tsx index 423ad83d..74a4b36f 100644 --- a/apps/web/src/app/[locale]/shop/_petGotcha/TenPet.tsx +++ b/apps/web/src/app/[locale]/shop/_petGotcha/TenPet.tsx @@ -9,10 +9,10 @@ import { cn } from '@gitanimals/ui-tailwind/utils'; import { useQueryClient } from '@tanstack/react-query'; import { toast } from 'sonner'; -import { sendMessageToErrorChannel } from '@/apis/slack/sendMessage'; -import { GITHUB_ISSUE_URL } from '@/constants/outlink'; -import { useTimer } from '@/hooks/useTimer'; -import { trackEvent } from '@/lib/analytics'; +import { sendMessageToErrorChannel } from '@/shared/api/slack'; +import { GITHUB_ISSUE_URL } from '@/shared/config/outlink'; +import { useTimer } from '@/shared/hooks/useTimer'; +import { trackEvent } from '@/shared/lib/analytics'; import { TenCardFlipGame } from './TenCardFlipGame'; import { useCheckEnoughMoney } from './useCheckEnoughMoney'; diff --git a/apps/web/src/app/[locale]/shop/page.tsx b/apps/web/src/app/[locale]/shop/page.tsx index 69cde40c..0bd3c610 100644 --- a/apps/web/src/app/[locale]/shop/page.tsx +++ b/apps/web/src/app/[locale]/shop/page.tsx @@ -2,7 +2,7 @@ import type { Metadata } from 'next'; import { notFound } from 'next/navigation'; import { getTranslations } from 'next-intl/server'; -import GNB from '@/components/GNB/GNB'; +import GNB from '@/widgets/gnb/GNB'; import { BackgroundSection } from './_background/BackgroundSection'; import { FloatingPointSection } from './_common/FloatingPointSection'; diff --git a/apps/web/src/app/[locale]/shop/useSearchOptions.ts b/apps/web/src/app/[locale]/shop/useSearchOptions.ts index fa40fe18..35baf87e 100644 --- a/apps/web/src/app/[locale]/shop/useSearchOptions.ts +++ b/apps/web/src/app/[locale]/shop/useSearchOptions.ts @@ -4,7 +4,7 @@ import { useMemo } from 'react'; import { useSearchParams } from 'next/navigation'; import type { OrderType, SortDirection } from '@gitanimals/api'; -import { usePathname, useRouter } from '@/i18n/routing'; +import { usePathname, useRouter } from '@/shared/i18n/routing'; interface SearchOptions { personaType: string; diff --git a/apps/web/src/app/[locale]/test/ranking/page.tsx b/apps/web/src/app/[locale]/test/ranking/page.tsx index 66b34adb..d2b98335 100644 --- a/apps/web/src/app/[locale]/test/ranking/page.tsx +++ b/apps/web/src/app/[locale]/test/ranking/page.tsx @@ -2,7 +2,7 @@ import { getServerSession } from 'next-auth'; import { getRankByUsername } from '@gitanimals/api'; import { rankQueries } from '@gitanimals/react-query'; -import { getDehydratedQueries, Hydrate } from '@/lib/react-query/queryClient'; +import { getDehydratedQueries, Hydrate } from '@/shared/lib/react-query/queryClient'; import { RANKS_PER_PAGE, RANKS_TOP_3 } from '../../landing/RankingSection/constants'; import RankingSection from '../../landing/RankingSection/RankingSection'; diff --git a/apps/web/src/app/api/auth/[...nextauth]/route.ts b/apps/web/src/app/api/auth/[...nextauth]/route.ts index 5543f4f5..aca80cb6 100644 --- a/apps/web/src/app/api/auth/[...nextauth]/route.ts +++ b/apps/web/src/app/api/auth/[...nextauth]/route.ts @@ -1,6 +1,6 @@ import NextAuth from 'next-auth'; -import { config } from '@/auth'; +import { config } from '@/shared/api/auth'; const handler = NextAuth(config); diff --git a/apps/web/src/app/api/auth/callback/rn-webview/route.ts b/apps/web/src/app/api/auth/callback/rn-webview/route.ts index 8db52efe..c3f65b28 100644 --- a/apps/web/src/app/api/auth/callback/rn-webview/route.ts +++ b/apps/web/src/app/api/auth/callback/rn-webview/route.ts @@ -1,7 +1,7 @@ import type { NextRequest } from 'next/server'; import NextAuth from 'next-auth'; -import { authOptions } from '@/auth'; +import { authOptions } from '@/shared/api/auth'; export async function POST(req: NextRequest) { try { diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index 4878fba5..5fdd2236 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -6,7 +6,7 @@ import { interceptorRequestFulfilled, interceptorResponseFulfilled, interceptorResponseRejected, -} from '@/apis/interceptor'; +} from '@/shared/api/interceptor'; import './globals.css'; import '@gitanimals/asset-font/product-sans/index.css'; diff --git a/apps/web/src/app/not-found.tsx b/apps/web/src/app/not-found.tsx index b9adf16d..8877a753 100644 --- a/apps/web/src/app/not-found.tsx +++ b/apps/web/src/app/not-found.tsx @@ -3,10 +3,10 @@ import { useEffect } from 'react'; import { usePathname, useRouter } from 'next/navigation'; -import { sendMessageToErrorChannel } from '@/apis/slack/sendMessage'; +import { sendMessageToErrorChannel } from '@/shared/api/slack'; import { ErrorPage } from '@/components/Error/ErrorPage'; -import { isDev } from '@/constants/env'; -import { GITHUB_ISSUE_URL } from '@/constants/outlink'; +import { isDev } from '@/shared/config/env'; +import { GITHUB_ISSUE_URL } from '@/shared/config/outlink'; export default function NotFound() { const pathname = usePathname(); diff --git a/apps/web/src/app/robots.ts b/apps/web/src/app/robots.ts index 06566013..73d91d49 100644 --- a/apps/web/src/app/robots.ts +++ b/apps/web/src/app/robots.ts @@ -1,7 +1,7 @@ import type { MetadataRoute } from 'next'; -import { config } from '@/constants/config'; -import { LOCALE_LIST } from '@/i18n/routing'; +import { config } from '@/shared/config/config'; +import { LOCALE_LIST } from '@/shared/i18n/routing'; export default function robots(): MetadataRoute.Robots { const baseUrl = config.url; diff --git a/apps/web/src/app/sitemap.ts b/apps/web/src/app/sitemap.ts index ecbb1914..13c426da 100644 --- a/apps/web/src/app/sitemap.ts +++ b/apps/web/src/app/sitemap.ts @@ -1,7 +1,7 @@ import type { MetadataRoute } from 'next'; -import { config } from '@/constants/config'; -import { LOCALE_LIST } from '@/i18n/routing'; +import { config } from '@/shared/config/config'; +import { LOCALE_LIST } from '@/shared/i18n/routing'; export default async function sitemap(): Promise { const baseUrl = config.url; diff --git a/apps/web/src/components/AdaptiveLink.tsx b/apps/web/src/components/AdaptiveLink.tsx index 6b3a3fbb..8aefb6ce 100644 --- a/apps/web/src/components/AdaptiveLink.tsx +++ b/apps/web/src/components/AdaptiveLink.tsx @@ -1,7 +1,7 @@ import type { AnchorHTMLAttributes } from 'react'; import { isExternalLink } from '@gitanimals/util-common'; -import { Link } from '@/i18n/routing'; +import { Link } from '@/shared/i18n/routing'; /** * 제공된 href에 따라 외부 링크를 위한 태그 또는 내부 경로를 위한 Next.js 컴포넌트를 렌더링 diff --git a/apps/web/src/components/AnimalCard/index.tsx b/apps/web/src/components/AnimalCard/index.tsx deleted file mode 100644 index 5971dd1e..00000000 --- a/apps/web/src/components/AnimalCard/index.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import AnimalCard from './AnimalCard'; - -export { AnimalCard }; diff --git a/apps/web/src/components/AuthButton.tsx b/apps/web/src/components/AuthButton.tsx index 656bd2d6..aaa13e1d 100644 --- a/apps/web/src/components/AuthButton.tsx +++ b/apps/web/src/components/AuthButton.tsx @@ -5,8 +5,8 @@ import { signOut } from 'next-auth/react'; import { useTranslations } from 'next-intl'; import { Button } from '@gitanimals/ui-tailwind'; -import { getGithubOauthUrl } from '@/apis/auth/getGithubOauth'; -import { COOKIE_KEY, LOCAL_STORAGE_KEY } from '@/constants/storage'; +import { getGithubOauthUrl } from '@/features/auth/api/getGithubOauth'; +import { COOKIE_KEY, LOCAL_STORAGE_KEY } from '@/shared/config/storage'; /** * client용 로그인 함수 diff --git a/apps/web/src/components/CardGame/FanDrawingGame/FanDrawingGame.tsx b/apps/web/src/components/CardGame/FanDrawingGame/FanDrawingGame.tsx index d79384d9..5220bdb0 100644 --- a/apps/web/src/components/CardGame/FanDrawingGame/FanDrawingGame.tsx +++ b/apps/web/src/components/CardGame/FanDrawingGame/FanDrawingGame.tsx @@ -7,7 +7,7 @@ import { useTranslations } from 'next-intl'; import { CardBack as CardBackUi } from '@gitanimals/ui-tailwind'; import { motion } from 'framer-motion'; -import { AnimalCard } from '@/components/AnimalCard'; +import { AnimalCard } from '@/entities/persona'; import { DrawingCardMotion, NonSelectedCardMotion } from './CardMotion'; diff --git a/apps/web/src/components/DevMode/DevModePage.tsx b/apps/web/src/components/DevMode/DevModePage.tsx index ee470d57..09d970e0 100644 --- a/apps/web/src/components/DevMode/DevModePage.tsx +++ b/apps/web/src/components/DevMode/DevModePage.tsx @@ -2,7 +2,7 @@ import { useSearchParams } from 'next/navigation'; -import { DEV_MODE_KEY, parseDevModeFromSearchParams } from '@/lib/devtools/constants'; +import { DEV_MODE_KEY, parseDevModeFromSearchParams } from '@/shared/lib/devtools/constants'; const DevModePage = ({ children }: { children: React.ReactNode }) => { const searchParams = useSearchParams(); diff --git a/apps/web/src/components/Global/ClientProvider.tsx b/apps/web/src/components/Global/ClientProvider.tsx index a7b133b7..cea54bba 100644 --- a/apps/web/src/components/Global/ClientProvider.tsx +++ b/apps/web/src/components/Global/ClientProvider.tsx @@ -5,7 +5,7 @@ import { SessionProvider } from 'next-auth/react'; import { domMax, LazyMotion } from 'framer-motion'; import { OverlayProvider } from 'overlay-kit'; -import QueryClientProvider from '@/apis/QueryClientProvider'; +import QueryClientProvider from '@/shared/api/QueryClientProvider'; import Monitoring from '@/components/Global/Monitoring'; import SessionLoader from './SessionLoader'; diff --git a/apps/web/src/components/Global/FeedbackForm.tsx b/apps/web/src/components/Global/FeedbackForm.tsx index 93d6eded..724af83d 100644 --- a/apps/web/src/components/Global/FeedbackForm.tsx +++ b/apps/web/src/components/Global/FeedbackForm.tsx @@ -7,15 +7,15 @@ import { XIcon } from '@gitanimals/ui-icon'; import { Button } from '@gitanimals/ui-tailwind'; import { toast } from 'sonner'; -import { usePostFeedback } from '@/apis/github/usePostFeedback'; -import type { PostIssueRequest } from '@/apis/github/usePostIssue'; +import { usePostFeedback } from '@/features/feedback/model/usePostFeedback'; +import type { PostIssueRequest } from '@/features/feedback/model/usePostIssue'; import Input from '@/components/Input'; import Select from '@/components/Select'; import TextArea from '@/components/TextArea'; -import type { GithubIssueType } from '@/constants/github'; -import { GITHUB_ISSUE_TYPE, SERVICE_MAINTAINER } from '@/constants/github'; -import { useClientUser } from '@/utils/clientAuth'; -import { sendLog } from '@/utils/log'; +import type { GithubIssueType } from '@/shared/config/github'; +import { GITHUB_ISSUE_TYPE, SERVICE_MAINTAINER } from '@/shared/config/github'; +import { useClientUser } from '@/shared/utils/clientAuth'; +import { sendLog } from '@/shared/utils/log'; const ISSUE_LABEL: Record< string, diff --git a/apps/web/src/components/Global/Monitoring.tsx b/apps/web/src/components/Global/Monitoring.tsx index 01146013..55d7f06e 100644 --- a/apps/web/src/components/Global/Monitoring.tsx +++ b/apps/web/src/components/Global/Monitoring.tsx @@ -6,10 +6,10 @@ import Script from 'next/script'; import { GoogleAnalytics, GoogleTagManager } from '@next/third-parties/google'; import { Analytics } from '@vercel/analytics/react'; -import { config, MONITORING_KEY } from '@/constants/config'; -import { isProd } from '@/constants/env'; -import { usePathname } from '@/i18n/routing'; -import { initAnalytics, trackPageView } from '@/lib/analytics'; +import { config, MONITORING_KEY } from '@/shared/config/config'; +import { isProd } from '@/shared/config/env'; +import { usePathname } from '@/shared/i18n/routing'; +import { initAnalytics, trackPageView } from '@/shared/lib/analytics'; function Monitoring() { const pathname = usePathname(); diff --git a/apps/web/src/components/Global/SessionLoader.tsx b/apps/web/src/components/Global/SessionLoader.tsx index a5718f1d..118a3ec4 100644 --- a/apps/web/src/components/Global/SessionLoader.tsx +++ b/apps/web/src/components/Global/SessionLoader.tsx @@ -8,7 +8,7 @@ import { interceptorRequestFulfilled, interceptorResponseFulfilled, interceptorResponseRejected, -} from '@/apis/interceptor'; +} from '@/shared/api/interceptor'; const SessionLoader = ({ children }: { children: React.ReactNode }) => { const session = useSession(); diff --git a/apps/web/src/components/Global/useDialog.tsx b/apps/web/src/components/Global/useDialog.tsx index 94f61bb5..0bb80a39 100644 --- a/apps/web/src/components/Global/useDialog.tsx +++ b/apps/web/src/components/Global/useDialog.tsx @@ -1,8 +1,8 @@ 'use client'; -import { type ReactNode, useState } from 'react'; +import type { ReactNode } from 'react'; import { useTranslations } from 'next-intl'; -import { Button, cn, Dialog } from '@gitanimals/ui-tailwind'; +import { AlertDialog, ConfirmDialog } from '@gitanimals/ui-tailwind'; import { atom, useAtom } from 'jotai'; interface DialogState { @@ -51,8 +51,8 @@ export function useDialog() { */ export function DialogComponent() { const [dialog, setDialog] = useAtom(dialogAtom); - const [isLoading, setIsLoading] = useState(false); const t = useTranslations('Common'); + const closeDialog = () => { setDialog((prev) => ({ ...prev, @@ -60,39 +60,27 @@ export function DialogComponent() { })); }; - const confirmDialog = async () => { - if (isLoading) return; - - if (dialog.onConfirm) { - setIsLoading(true); - await dialog.onConfirm(); - setIsLoading(false); - } - closeDialog(); - }; + if (dialog.onConfirm) { + return ( + + ); + } return ( - - - {dialog.title} - {dialog.description && ( - {dialog.description} - )} -
- - {dialog.onConfirm && ( - - )} -
-
-
+ ); } - -const titleStyle = cn('font-product text-glyph-20 text-left'); - -const descriptionStyle = cn('font-product text-glyph-16 text-left text-white/75 w-full'); diff --git a/apps/web/src/components/InterceptingDialog.tsx b/apps/web/src/components/InterceptingDialog.tsx deleted file mode 100644 index dcf66083..00000000 --- a/apps/web/src/components/InterceptingDialog.tsx +++ /dev/null @@ -1,36 +0,0 @@ -'use client'; - -import { type PropsWithChildren, useEffect, useState } from 'react'; -import { cn } from '@gitanimals/ui-tailwind'; -import { Dialog } from '@gitanimals/ui-tailwind'; - -import { usePathname, useRouter } from '@/i18n/routing'; -import { customScrollHorizontalStyle } from '@/styles/scrollStyle'; - -export function InterceptingDialog({ children }: PropsWithChildren) { - const router = useRouter(); - const pathname = usePathname(); - - const [isOpen, setIsOpen] = useState(false); - - const onClose = () => { - router.back(); - }; - - useEffect(() => { - if (!isOpen) { - setIsOpen(true); - } else { - setIsOpen(false); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [pathname]); - - return ( - - - {children} - - - ); -} diff --git a/apps/web/src/components/Layout/TabBar.tsx b/apps/web/src/components/Layout/TabBar.tsx index bc1e7062..7ad1a6da 100644 --- a/apps/web/src/components/Layout/TabBar.tsx +++ b/apps/web/src/components/Layout/TabBar.tsx @@ -2,7 +2,7 @@ import { cn } from '@gitanimals/ui-tailwind/utils'; import { wrap } from '@suspensive/react'; import { GamepadIcon, HouseIcon, StoreIcon, UserRoundIcon } from 'lucide-react'; -import { Link, usePathname } from '@/i18n/routing'; +import { Link, usePathname } from '@/shared/i18n/routing'; const TAB_LIST = [ { diff --git a/apps/web/src/components/PageModal/index.tsx b/apps/web/src/components/PageModal/index.tsx index 0f703a06..9b3c0876 100644 --- a/apps/web/src/components/PageModal/index.tsx +++ b/apps/web/src/components/PageModal/index.tsx @@ -5,7 +5,7 @@ import { XIcon } from 'lucide-react'; import { BackTrigger } from '@/components/Trigger'; const dialogTitleStyle = - 'font-product text-glyph-48 font-bold text-white text-center max-[1200px]:text-glyph-32 max-mobile:text-glyph-24'; + 'font-product text-glyph-48 font-bold text-white text-center max-1200:text-glyph-32 max-mobile:text-glyph-24'; export function PageModalLayout({ children }: PropsWithChildren) { return ( diff --git a/apps/web/src/components/Pagination/Pagination.tsx b/apps/web/src/components/Pagination/Pagination.tsx index d3a0c251..5f3d00dd 100644 --- a/apps/web/src/components/Pagination/Pagination.tsx +++ b/apps/web/src/components/Pagination/Pagination.tsx @@ -1,7 +1,7 @@ import { cn } from '@gitanimals/ui-tailwind/utils'; import { ChevronLeft, ChevronRight } from 'lucide-react'; -import type { PaginationSchema } from '@/schema/pagination'; +import type { PaginationSchema } from '@/shared/schema/pagination'; function Pagination(props: { currentPage: number; onSetPage: (page: number) => void } & PaginationSchema) { const getPaginationGroup = () => { diff --git a/apps/web/src/components/Pagination/PaginationServer.tsx b/apps/web/src/components/Pagination/PaginationServer.tsx index bc77b63c..414981c9 100644 --- a/apps/web/src/components/Pagination/PaginationServer.tsx +++ b/apps/web/src/components/Pagination/PaginationServer.tsx @@ -1,8 +1,8 @@ import { cn } from '@gitanimals/ui-tailwind/utils'; import { ChevronLeft, ChevronRight } from 'lucide-react'; -import { Link } from '@/i18n/routing'; -import type { PaginationSchema } from '@/schema/pagination'; +import { Link } from '@/shared/i18n/routing'; +import type { PaginationSchema } from '@/shared/schema/pagination'; export function PaginationServer(props: { generateMoveLink: (props: { page: number }) => string } & PaginationSchema) { const getPaginationGroup = () => { diff --git a/apps/web/src/components/PersonaListToolbar.tsx b/apps/web/src/components/PersonaListToolbar.tsx deleted file mode 100644 index b4dc995f..00000000 --- a/apps/web/src/components/PersonaListToolbar.tsx +++ /dev/null @@ -1,232 +0,0 @@ -'use client'; - -import { useTranslations } from 'next-intl'; -import { css } from '_panda/css'; -import { flex } from '_panda/patterns'; -import { CombineChip, SearchBar } from '@gitanimals/ui-panda'; -import { RotateCcwIcon } from 'lucide-react'; - -import { ANIMAL_TIER_INFO } from '@/components/AnimalCard/AnimalCard.constant'; -import type { - GradeFilter, - PersonaFilterState, - SortBy, - TierFilter, - VisibilityFilter, -} from '@/hooks/persona/usePersonaListFilter'; - -export interface PersonaListToolbarProps { - filterState: PersonaFilterState; - onFilterChange: (partial: Partial) => void; - onReset: () => void; - counts: { filtered: number; total: number }; - isFiltering: boolean; - /** 검색바 표시 여부 */ - showSearch?: boolean; - /** 가시성 필터 표시 여부 (Farm 컨텍스트) */ - showVisibilityFilter?: boolean; - /** 진화 가능 필터 표시 여부 */ - showEvolvableFilter?: boolean; -} - -const GRADE_OPTIONS: { value: GradeFilter; labelKey: string }[] = [ - { value: 'ALL', labelKey: 'filter-all' }, - { value: 'COLLABORATOR', labelKey: 'filter-grade-collaborator' }, - { value: 'DEFAULT', labelKey: 'filter-grade-default' }, - { value: 'EVOLUTION', labelKey: 'filter-grade-evolution' }, -]; - -const TIER_OPTIONS: { value: TierFilter; label: string }[] = [ - { value: 'ALL', label: 'ALL' }, - { value: 'EX', label: ANIMAL_TIER_INFO.EX.label }, - { value: 'S_PLUS', label: ANIMAL_TIER_INFO.S_PLUS.label }, - { value: 'A_PLUS', label: ANIMAL_TIER_INFO.A_PLUS.label }, - { value: 'B_MINUS', label: ANIMAL_TIER_INFO.B_MINUS.label }, -]; - -const VISIBILITY_OPTIONS: { value: VisibilityFilter; labelKey: string }[] = [ - { value: 'ALL', labelKey: 'filter-all' }, - { value: 'VISIBLE', labelKey: 'filter-visible' }, - { value: 'HIDDEN', labelKey: 'filter-hidden' }, -]; - -const SORT_OPTIONS: { value: SortBy; labelKey: string }[] = [ - { value: 'grade', labelKey: 'sort-grade' }, - { value: 'level-desc', labelKey: 'sort-level-desc' }, - { value: 'level-asc', labelKey: 'sort-level-asc' }, - { value: 'tier', labelKey: 'sort-tier' }, - { value: 'name', labelKey: 'sort-name' }, -]; - -export function PersonaListToolbar({ - filterState, - onFilterChange, - onReset, - counts, - isFiltering, - showSearch = false, - showVisibilityFilter = false, - showEvolvableFilter = false, -}: PersonaListToolbarProps) { - const t = useTranslations('Mypage.Filter'); - - return ( -
- {showSearch && ( - onFilterChange({ searchQuery: e.target.value })} - /> - )} - -
- {/* 등급 필터 */} - onFilterChange({ grade: v as GradeFilter })} - > - - - - - {GRADE_OPTIONS.map((opt) => ( - - {t(opt.labelKey)} - - ))} - - - - {/* 티어 필터 */} - onFilterChange({ tier: v as TierFilter })}> - - - - - {TIER_OPTIONS.map((opt) => ( - - {opt.label} - - ))} - - - - {/* 가시성 필터 */} - {showVisibilityFilter && ( - onFilterChange({ visibility: v as VisibilityFilter })} - > - - - - - {VISIBILITY_OPTIONS.map((opt) => ( - - {t(opt.labelKey)} - - ))} - - - )} - - {/* 진화 가능 필터 */} - {showEvolvableFilter && ( - - )} - - {/* 정렬 */} - onFilterChange({ sortBy: v as SortBy })}> - - - - - {SORT_OPTIONS.map((opt) => ( - - {t(opt.labelKey)} - - ))} - - - - {/* 결과 수 + 초기화 */} -
- - {counts.filtered}/{counts.total} - - {isFiltering && ( - - )} -
-
-
- ); -} - -const toolbarContainerStyle = css({ - display: 'flex', - flexDirection: 'column', - gap: '8px', - marginBottom: '8px', -}); - -const filterRowStyle = flex({ - gap: '6px', - alignItems: 'center', - flexWrap: 'wrap', -}); - -const toggleButtonStyle = css({ - height: '30px', - padding: '6px 12px', - borderRadius: '6px', - textStyle: 'glyph12.regular', - color: 'white.white_50', - backgroundColor: 'white.white_5', - border: '1px solid transparent', - cursor: 'pointer', - transition: 'all 0.15s ease', - _hover: { - backgroundColor: 'white.white_10', - }, - '&[data-active]': { - color: 'white.white_90', - backgroundColor: 'brand.sky_25', - borderColor: 'brand.sky', - }, -}); - -const countSectionStyle = flex({ - alignItems: 'center', - gap: '4px', - marginLeft: 'auto', -}); - -const countTextStyle = css({ - textStyle: 'glyph12.regular', - color: 'white.white_50', -}); - -const resetButtonStyle = css({ - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - width: '24px', - height: '24px', - borderRadius: '4px', - color: 'white.white_50', - cursor: 'pointer', - _hover: { - backgroundColor: 'white.white_10', - color: 'white.white_75', - }, -}); diff --git a/apps/web/src/components/Responsive/Responsive.tsx b/apps/web/src/components/Responsive/Responsive.tsx index 99888673..bfba6f6b 100644 --- a/apps/web/src/components/Responsive/Responsive.tsx +++ b/apps/web/src/components/Responsive/Responsive.tsx @@ -1,8 +1,8 @@ import type { ComponentProps, ElementType } from 'react'; -import { css, cx } from '_panda/css'; +import { cn } from '@gitanimals/ui-tailwind/utils'; -const desktopClass = css({ display: 'block', _mobile: { display: 'none' } }); -const mobileClass = css({ display: 'none', _mobile: { display: 'block' } }); +const desktopClass = 'block max-mobile:hidden'; +const mobileClass = 'hidden max-mobile:block'; type ResponsiveProps = { component: C; @@ -22,10 +22,10 @@ export function Responsive({ return ( <> - + {children} - + {children} diff --git a/apps/web/src/components/RouteModal.tsx b/apps/web/src/components/RouteModal.tsx index 2c4507b3..93ac338d 100644 --- a/apps/web/src/components/RouteModal.tsx +++ b/apps/web/src/components/RouteModal.tsx @@ -4,8 +4,8 @@ import { type PropsWithChildren, useEffect, useState } from 'react'; import { cn } from '@gitanimals/ui-tailwind'; import { Dialog } from '@gitanimals/ui-tailwind'; -import { usePathname, useRouter } from '@/i18n/routing'; -import { customScrollHorizontalStyle } from '@/styles/scrollStyle'; +import { usePathname, useRouter } from '@/shared/i18n/routing'; +import { customScrollHorizontalStyle } from '@/shared/styles/scrollStyle'; /** * 라우트 모달 컴포넌트 @@ -18,7 +18,12 @@ import { customScrollHorizontalStyle } from '@/styles/scrollStyle'; * @param title - 모달 제목 (선택사항) */ -export default function RouteModal({ children, title }: PropsWithChildren<{ title?: string }>) { +interface RouteModalProps { + title?: string; + gap?: 'sm' | 'lg'; +} + +export default function RouteModal({ children, title, gap = 'sm' }: PropsWithChildren) { const router = useRouter(); const pathname = usePathname(); @@ -41,7 +46,11 @@ export default function RouteModal({ children, title }: PropsWithChildren<{ titl {title && {title}} {children} diff --git a/apps/web/src/components/Slider/PerspectiveCenterSlider.tsx b/apps/web/src/components/Slider/PerspectiveCenterSlider.tsx index ab93fd85..22130371 100644 --- a/apps/web/src/components/Slider/PerspectiveCenterSlider.tsx +++ b/apps/web/src/components/Slider/PerspectiveCenterSlider.tsx @@ -7,7 +7,7 @@ import type { FlickingOptions, FlickingProps } from '@egjs/react-flicking'; import Flicking from '@egjs/react-flicking'; import { cn } from '@gitanimals/ui-tailwind'; -import { useIsMounted } from '@/hooks/useIsMounted'; +import { useIsMounted } from '@/shared/hooks/useIsMounted'; /** * 중앙에 있는 카드를 3D 효과로 보여주는 슬라이더 diff --git a/apps/web/src/components/Trigger/BackTrigger.tsx b/apps/web/src/components/Trigger/BackTrigger.tsx index 1d0031b2..e20fea7d 100644 --- a/apps/web/src/components/Trigger/BackTrigger.tsx +++ b/apps/web/src/components/Trigger/BackTrigger.tsx @@ -2,7 +2,7 @@ import type { ComponentProps } from 'react'; -import { useRouter } from '@/i18n/routing'; +import { useRouter } from '@/shared/i18n/routing'; /** * 이전 페이지로 이동하는 버튼 컴포넌트 diff --git a/apps/web/src/entities/guild/index.ts b/apps/web/src/entities/guild/index.ts new file mode 100644 index 00000000..5ecdd1f3 --- /dev/null +++ b/apps/web/src/entities/guild/index.ts @@ -0,0 +1 @@ +export * from './ui'; diff --git a/apps/web/src/components/Guild/MemeberSlider.tsx b/apps/web/src/entities/guild/ui/GuildMemberSlider.tsx similarity index 93% rename from apps/web/src/components/Guild/MemeberSlider.tsx rename to apps/web/src/entities/guild/ui/GuildMemberSlider.tsx index a8e7a179..203e823f 100644 --- a/apps/web/src/components/Guild/MemeberSlider.tsx +++ b/apps/web/src/entities/guild/ui/GuildMemberSlider.tsx @@ -4,7 +4,7 @@ import type { GuildMember } from '@gitanimals/api'; import { BannerPetSelectMedium } from '@gitanimals/ui-tailwind'; import useEmblaCarousel from 'embla-carousel-react'; -import { getPersonaImage } from '@/utils/image'; +import { getPersonaImage } from '@/shared/utils/image'; export function GuildMemeberSlider({ members }: { members: GuildMember[] }) { const [emblaRef] = useEmblaCarousel({ diff --git a/apps/web/src/entities/guild/ui/index.ts b/apps/web/src/entities/guild/ui/index.ts new file mode 100644 index 00000000..678a7758 --- /dev/null +++ b/apps/web/src/entities/guild/ui/index.ts @@ -0,0 +1 @@ +export { GuildMemeberSlider } from './GuildMemberSlider'; diff --git a/apps/web/src/entities/persona/index.ts b/apps/web/src/entities/persona/index.ts new file mode 100644 index 00000000..c51f0c57 --- /dev/null +++ b/apps/web/src/entities/persona/index.ts @@ -0,0 +1,2 @@ +export * from './ui'; +export * from './model'; diff --git a/apps/web/src/entities/persona/model/index.ts b/apps/web/src/entities/persona/model/index.ts new file mode 100644 index 00000000..c35d7173 --- /dev/null +++ b/apps/web/src/entities/persona/model/index.ts @@ -0,0 +1,4 @@ +export { useGetAllPersona } from './useGetAllPersona'; +export { useGetTotalPersonaCount } from './useGetTotalPersonaCount'; +export { usePersonaListFilter, type PersonaFilterState, type SortBy, type GradeFilter, type TierFilter, type VisibilityFilter } from './usePersonaListFilter'; +export { useGetPersonaDropRate, useGetPersonaTier } from './useGetPersonaDropRate'; diff --git a/apps/web/src/hooks/query/render/useGetAllPersona.ts b/apps/web/src/entities/persona/model/useGetAllPersona.ts similarity index 100% rename from apps/web/src/hooks/query/render/useGetAllPersona.ts rename to apps/web/src/entities/persona/model/useGetAllPersona.ts diff --git a/apps/web/src/hooks/persona/useGetPersonaDropRate.ts b/apps/web/src/entities/persona/model/useGetPersonaDropRate.ts similarity index 85% rename from apps/web/src/hooks/persona/useGetPersonaDropRate.ts rename to apps/web/src/entities/persona/model/useGetPersonaDropRate.ts index 2d920595..f5f308ed 100644 --- a/apps/web/src/hooks/persona/useGetPersonaDropRate.ts +++ b/apps/web/src/entities/persona/model/useGetPersonaDropRate.ts @@ -1,6 +1,6 @@ -import { getAnimalTierInfo } from '@/utils/animals'; +import { getAnimalTierInfo } from '@/shared/utils/animals'; -import { useGetAllPersona } from '../query/render/useGetAllPersona'; +import { useGetAllPersona } from './useGetAllPersona'; /** * 페르소나의 드랍률을 가져오는 훅 diff --git a/apps/web/src/hooks/query/render/useGetTotalPersonaCount.ts b/apps/web/src/entities/persona/model/useGetTotalPersonaCount.ts similarity index 100% rename from apps/web/src/hooks/query/render/useGetTotalPersonaCount.ts rename to apps/web/src/entities/persona/model/useGetTotalPersonaCount.ts diff --git a/apps/web/src/hooks/persona/usePersonaListFilter.ts b/apps/web/src/entities/persona/model/usePersonaListFilter.ts similarity index 95% rename from apps/web/src/hooks/persona/usePersonaListFilter.ts rename to apps/web/src/entities/persona/model/usePersonaListFilter.ts index 9c09cfd6..9290932e 100644 --- a/apps/web/src/hooks/persona/usePersonaListFilter.ts +++ b/apps/web/src/entities/persona/model/usePersonaListFilter.ts @@ -1,8 +1,8 @@ import { useDeferredValue, useMemo, useState } from 'react'; import type { Persona } from '@gitanimals/api'; -import type { AnimalTierType } from '@/components/AnimalCard/AnimalCard.constant'; -import { getAnimalTierInfo, getPersonaGradePriority } from '@/utils/animals'; +import type { AnimalTierType } from '@/shared/config/animalTier'; +import { getAnimalTierInfo, getPersonaGradePriority } from '@/shared/utils/animals'; export type SortBy = 'grade' | 'level-desc' | 'level-asc' | 'tier' | 'name'; export type GradeFilter = 'ALL' | 'COLLABORATOR' | 'DEFAULT' | 'EVOLUTION'; diff --git a/apps/web/src/components/AnimalCard/AnimalCard.tsx b/apps/web/src/entities/persona/ui/AnimalCard.tsx similarity index 90% rename from apps/web/src/components/AnimalCard/AnimalCard.tsx rename to apps/web/src/entities/persona/ui/AnimalCard.tsx index 18cde0f0..e4f7d281 100644 --- a/apps/web/src/components/AnimalCard/AnimalCard.tsx +++ b/apps/web/src/entities/persona/ui/AnimalCard.tsx @@ -2,8 +2,8 @@ import type { ComponentProps } from 'react'; import type { PersonaInfo } from '@gitanimals/api'; import { CardBack, type CardTierType, GameCard } from '@gitanimals/ui-tailwind'; -import { getAnimalTierInfo } from '@/utils/animals'; -import { getPersonaImage } from '@/utils/image'; +import { getAnimalTierInfo } from '@/shared/utils/animals'; +import { getPersonaImage } from '@/shared/utils/image'; interface AnimalCardProps extends Partial { type: string; diff --git a/apps/web/src/components/PersonaItem.tsx b/apps/web/src/entities/persona/ui/PersonaItem.tsx similarity index 81% rename from apps/web/src/components/PersonaItem.tsx rename to apps/web/src/entities/persona/ui/PersonaItem.tsx index 7711cfe1..476a72f3 100644 --- a/apps/web/src/components/PersonaItem.tsx +++ b/apps/web/src/entities/persona/ui/PersonaItem.tsx @@ -1,12 +1,11 @@ import type { ComponentProps } from 'react'; import { memo } from 'react'; -import { css, cx } from '_panda/css'; import type { Persona } from '@gitanimals/api'; -import { Banner, LevelBanner } from '@gitanimals/ui-panda'; +import { Banner, cn, LevelBanner } from '@gitanimals/ui-tailwind'; -import { getPersonaImage } from '@/utils/image'; +import { getPersonaImage } from '@/shared/utils/image'; -// --- LevelPersonaItem (LevelBanner 기반) --- +// --- LevelPersonaItem (LevelBanner — ui-tailwind) --- interface LevelPersonaItemProps { persona: Persona; @@ -19,8 +18,9 @@ interface LevelPersonaItemProps { function LevelPersonaItem({ persona, isSelected, onClick, size = 'full', className }: LevelPersonaItemProps) { return ( @@ -48,7 +48,7 @@ export const MemoizedLevelPersonaItem = memo(LevelPersonaItem, (prev, next) => { ); }); -// --- BannerPersonaItem (Banner 기반) --- +// --- BannerPersonaItem (Banner — ui-tailwind) --- interface BannerPersonaItemProps { persona: Persona; @@ -72,10 +72,11 @@ function BannerPersonaItem({ }: BannerPersonaItemProps) { return ( diff --git a/apps/web/src/entities/persona/ui/PersonaListToolbar.tsx b/apps/web/src/entities/persona/ui/PersonaListToolbar.tsx new file mode 100644 index 00000000..95545e41 --- /dev/null +++ b/apps/web/src/entities/persona/ui/PersonaListToolbar.tsx @@ -0,0 +1,178 @@ +'use client'; + +import { useTranslations } from 'next-intl'; +import { cn, SearchBar, Select } from '@gitanimals/ui-tailwind'; +import { RotateCcwIcon } from 'lucide-react'; + +import { ANIMAL_TIER_INFO } from '@/shared/config/animalTier'; +import type { + GradeFilter, + PersonaFilterState, + SortBy, + TierFilter, + VisibilityFilter, +} from '@/entities/persona/model/usePersonaListFilter'; + +export interface PersonaListToolbarProps { + filterState: PersonaFilterState; + onFilterChange: (partial: Partial) => void; + onReset: () => void; + counts: { filtered: number; total: number }; + isFiltering: boolean; + /** 검색바 표시 여부 */ + showSearch?: boolean; + /** 가시성 필터 표시 여부 (Farm 컨텍스트) */ + showVisibilityFilter?: boolean; + /** 진화 가능 필터 표시 여부 */ + showEvolvableFilter?: boolean; +} + +const GRADE_OPTIONS: { value: GradeFilter; labelKey: string }[] = [ + { value: 'ALL', labelKey: 'filter-all' }, + { value: 'COLLABORATOR', labelKey: 'filter-grade-collaborator' }, + { value: 'DEFAULT', labelKey: 'filter-grade-default' }, + { value: 'EVOLUTION', labelKey: 'filter-grade-evolution' }, +]; + +const TIER_OPTIONS: { value: TierFilter; label: string }[] = [ + { value: 'ALL', label: 'ALL' }, + { value: 'EX', label: ANIMAL_TIER_INFO.EX.label }, + { value: 'S_PLUS', label: ANIMAL_TIER_INFO.S_PLUS.label }, + { value: 'A_PLUS', label: ANIMAL_TIER_INFO.A_PLUS.label }, + { value: 'B_MINUS', label: ANIMAL_TIER_INFO.B_MINUS.label }, +]; + +const VISIBILITY_OPTIONS: { value: VisibilityFilter; labelKey: string }[] = [ + { value: 'ALL', labelKey: 'filter-all' }, + { value: 'VISIBLE', labelKey: 'filter-visible' }, + { value: 'HIDDEN', labelKey: 'filter-hidden' }, +]; + +const SORT_OPTIONS: { value: SortBy; labelKey: string }[] = [ + { value: 'grade', labelKey: 'sort-grade' }, + { value: 'level-desc', labelKey: 'sort-level-desc' }, + { value: 'level-asc', labelKey: 'sort-level-asc' }, + { value: 'tier', labelKey: 'sort-tier' }, + { value: 'name', labelKey: 'sort-name' }, +]; + +const filterTriggerClassName = cn( + 'min-w-0 max-w-[9.5rem] shrink-0', + 'font-product text-glyph-12 text-white/50', + 'border-white/10', +); + +export function PersonaListToolbar({ + filterState, + onFilterChange, + onReset, + counts, + isFiltering, + showSearch = false, + showVisibilityFilter = false, + showEvolvableFilter = false, +}: PersonaListToolbarProps) { + const t = useTranslations('Mypage.Filter'); + + return ( +
+ {showSearch && ( + onFilterChange({ searchQuery: e.target.value })} + /> + )} + +
+ + + + + {showVisibilityFilter && ( + + )} + + {showEvolvableFilter && ( + + )} + + + +
+ + {counts.filtered}/{counts.total} + + {isFiltering && ( + + )} +
+
+
+ ); +} diff --git a/apps/web/src/entities/persona/ui/index.ts b/apps/web/src/entities/persona/ui/index.ts new file mode 100644 index 00000000..63314df4 --- /dev/null +++ b/apps/web/src/entities/persona/ui/index.ts @@ -0,0 +1,8 @@ +export { default as AnimalCard, AnimalCardBack } from './AnimalCard'; +export { + MemoizedLevelPersonaItem, + MemoizedBannerPersonaItem, + MemoizedPersonaItem, + MemoizedPersonaBannerItem, +} from './PersonaItem'; +export { PersonaListToolbar, type PersonaListToolbarProps } from './PersonaListToolbar'; diff --git a/apps/web/src/entities/product/index.ts b/apps/web/src/entities/product/index.ts new file mode 100644 index 00000000..9f8ccadd --- /dev/null +++ b/apps/web/src/entities/product/index.ts @@ -0,0 +1 @@ +export * from './model'; diff --git a/apps/web/src/entities/product/model/index.ts b/apps/web/src/entities/product/model/index.ts new file mode 100644 index 00000000..0cb1dedf --- /dev/null +++ b/apps/web/src/entities/product/model/index.ts @@ -0,0 +1 @@ +export { useGetTotalProductCount } from './useGetTotalProductCount'; diff --git a/apps/web/src/hooks/query/auction/useGetTotalProductCount.ts b/apps/web/src/entities/product/model/useGetTotalProductCount.ts similarity index 100% rename from apps/web/src/hooks/query/auction/useGetTotalProductCount.ts rename to apps/web/src/entities/product/model/useGetTotalProductCount.ts diff --git a/apps/web/src/entities/user/index.ts b/apps/web/src/entities/user/index.ts new file mode 100644 index 00000000..9f8ccadd --- /dev/null +++ b/apps/web/src/entities/user/index.ts @@ -0,0 +1 @@ +export * from './model'; diff --git a/apps/web/src/entities/user/model/index.ts b/apps/web/src/entities/user/model/index.ts new file mode 100644 index 00000000..fa075b21 --- /dev/null +++ b/apps/web/src/entities/user/model/index.ts @@ -0,0 +1,2 @@ +export { useGetTotalRenderUserCount } from './useGetTotalRenderUserCount'; +export { useGetTotalIdentityUserCount } from './useGetTotalIdentityUserCount'; diff --git a/apps/web/src/hooks/query/identity/useGetTotalIdentityUserCount.ts b/apps/web/src/entities/user/model/useGetTotalIdentityUserCount.ts similarity index 100% rename from apps/web/src/hooks/query/identity/useGetTotalIdentityUserCount.ts rename to apps/web/src/entities/user/model/useGetTotalIdentityUserCount.ts diff --git a/apps/web/src/hooks/query/render/useGetTotalRenderUserCount.ts b/apps/web/src/entities/user/model/useGetTotalRenderUserCount.ts similarity index 100% rename from apps/web/src/hooks/query/render/useGetTotalRenderUserCount.ts rename to apps/web/src/entities/user/model/useGetTotalRenderUserCount.ts diff --git a/apps/web/src/features/auction/index.ts b/apps/web/src/features/auction/index.ts new file mode 100644 index 00000000..a896d05c --- /dev/null +++ b/apps/web/src/features/auction/index.ts @@ -0,0 +1 @@ +export { useRegisterProduct } from './model/useRegisterProduct'; diff --git a/apps/web/src/apis/auctions/useRegisterProduct.ts b/apps/web/src/features/auction/model/useRegisterProduct.ts similarity index 76% rename from apps/web/src/apis/auctions/useRegisterProduct.ts rename to apps/web/src/features/auction/model/useRegisterProduct.ts index 7b9fa1ce..252ba918 100644 --- a/apps/web/src/apis/auctions/useRegisterProduct.ts +++ b/apps/web/src/features/auction/model/useRegisterProduct.ts @@ -1,10 +1,10 @@ import type { UseMutationOptions } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query'; -import type { ApiErrorScheme } from '@/exceptions/type'; -import type { ProductHistorySchema } from '@/schema/action'; +import type { ApiErrorScheme } from '@/shared/exceptions/type'; +import type { ProductHistorySchema } from '@/shared/schema/action'; -import { post } from '..'; +import { post } from '@/shared/api/legacyClient'; interface RegisterProductRequest { personaId: string; diff --git a/apps/web/src/apis/auth/getGithubOauth.ts b/apps/web/src/features/auth/api/getGithubOauth.ts similarity index 100% rename from apps/web/src/apis/auth/getGithubOauth.ts rename to apps/web/src/features/auth/api/getGithubOauth.ts diff --git a/apps/web/src/features/auth/index.ts b/apps/web/src/features/auth/index.ts new file mode 100644 index 00000000..53ba62a8 --- /dev/null +++ b/apps/web/src/features/auth/index.ts @@ -0,0 +1 @@ +export { getGithubOauthUrl } from './api/getGithubOauth'; diff --git a/apps/web/src/features/change-persona-visible/index.ts b/apps/web/src/features/change-persona-visible/index.ts new file mode 100644 index 00000000..5dc03bcb --- /dev/null +++ b/apps/web/src/features/change-persona-visible/index.ts @@ -0,0 +1 @@ +export { useChangePersonaVisible } from './model/useChangePersonaVisible'; diff --git a/apps/web/src/apis/persona/useChangePersonaVisible.ts b/apps/web/src/features/change-persona-visible/model/useChangePersonaVisible.ts similarity index 96% rename from apps/web/src/apis/persona/useChangePersonaVisible.ts rename to apps/web/src/features/change-persona-visible/model/useChangePersonaVisible.ts index 9b560dda..bfc6bc28 100644 --- a/apps/web/src/apis/persona/useChangePersonaVisible.ts +++ b/apps/web/src/features/change-persona-visible/model/useChangePersonaVisible.ts @@ -3,7 +3,7 @@ import type { UseMutationOptions } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query'; import type { AxiosError, AxiosResponse } from 'axios'; -import { renderPatch } from '../render'; +import { renderPatch } from '@/shared/api/render'; interface ChangePersonaVisibleRequest { personaId: string; diff --git a/apps/web/src/apis/github/core.ts b/apps/web/src/features/feedback/api/github.ts similarity index 100% rename from apps/web/src/apis/github/core.ts rename to apps/web/src/features/feedback/api/github.ts diff --git a/apps/web/src/features/feedback/index.ts b/apps/web/src/features/feedback/index.ts new file mode 100644 index 00000000..bda121a2 --- /dev/null +++ b/apps/web/src/features/feedback/index.ts @@ -0,0 +1,3 @@ +export { usePostFeedback, type PostFeedbackRequest } from './model/usePostFeedback'; +export { usePostIssue, type PostIssueRequest } from './model/usePostIssue'; +export { createComment, type CreateCommentRequest } from './model/useCreateComment'; diff --git a/apps/web/src/apis/github/useCreateComment.ts b/apps/web/src/features/feedback/model/useCreateComment.ts similarity index 87% rename from apps/web/src/apis/github/useCreateComment.ts rename to apps/web/src/features/feedback/model/useCreateComment.ts index 1fd74088..f3ff88f3 100644 --- a/apps/web/src/apis/github/useCreateComment.ts +++ b/apps/web/src/features/feedback/model/useCreateComment.ts @@ -1,4 +1,4 @@ -import { requestOctokit } from './core'; +import { requestOctokit } from '../api/github'; export interface CreateCommentRequest { issueNumber: number; diff --git a/apps/web/src/apis/github/usePostFeedback.ts b/apps/web/src/features/feedback/model/usePostFeedback.ts similarity index 100% rename from apps/web/src/apis/github/usePostFeedback.ts rename to apps/web/src/features/feedback/model/usePostFeedback.ts diff --git a/apps/web/src/apis/github/usePostIssue.ts b/apps/web/src/features/feedback/model/usePostIssue.ts similarity index 94% rename from apps/web/src/apis/github/usePostIssue.ts rename to apps/web/src/features/feedback/model/usePostIssue.ts index c887e844..5d87a20e 100644 --- a/apps/web/src/apis/github/usePostIssue.ts +++ b/apps/web/src/features/feedback/model/usePostIssue.ts @@ -1,7 +1,7 @@ import type { UseMutationOptions } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query'; -import { requestOctokit } from './core'; +import { requestOctokit } from '../api/github'; export interface PostIssueRequest { title: string; diff --git a/apps/web/src/apis/laboratory/feedback.ts b/apps/web/src/features/laboratory-feedback/api/feedback.ts similarity index 98% rename from apps/web/src/apis/laboratory/feedback.ts rename to apps/web/src/features/laboratory-feedback/api/feedback.ts index 33914384..7996e052 100644 --- a/apps/web/src/apis/laboratory/feedback.ts +++ b/apps/web/src/features/laboratory-feedback/api/feedback.ts @@ -1,4 +1,4 @@ -import { supabase } from '@/lib/supabase/client'; +import { supabase } from '@/shared/lib/supabase/client'; export interface LaboratoryUpvote { id: string; diff --git a/apps/web/src/features/laboratory-feedback/index.ts b/apps/web/src/features/laboratory-feedback/index.ts new file mode 100644 index 00000000..135bf15f --- /dev/null +++ b/apps/web/src/features/laboratory-feedback/index.ts @@ -0,0 +1,3 @@ +export { createOrUpdateUpvote, checkUserUpvote, getAllUpvotes, getUpvoteCount } from './api/feedback'; +export type { LaboratoryUpvote, CreateUpvoteRequest, CheckUpvoteResponse } from './api/feedback'; +export { LABORATORY_FEEDBACK_QUERY_KEYS, upvoteQueryOptions } from './model/useLaboratoryFeedback'; diff --git a/apps/web/src/apis/laboratory/useLaboratoryFeedback.ts b/apps/web/src/features/laboratory-feedback/model/useLaboratoryFeedback.ts similarity index 98% rename from apps/web/src/apis/laboratory/useLaboratoryFeedback.ts rename to apps/web/src/features/laboratory-feedback/model/useLaboratoryFeedback.ts index a6e0ea48..538343fd 100644 --- a/apps/web/src/apis/laboratory/useLaboratoryFeedback.ts +++ b/apps/web/src/features/laboratory-feedback/model/useLaboratoryFeedback.ts @@ -1,6 +1,6 @@ import { queryOptions } from '@tanstack/react-query'; -import { checkUserUpvote, getAllUpvotes, getUpvoteCount } from './feedback'; +import { checkUserUpvote, getAllUpvotes, getUpvoteCount } from '../api/feedback'; export const LABORATORY_FEEDBACK_QUERY_KEYS = { all: ['laboratory-upvote'] as const, diff --git a/apps/web/src/middleware.ts b/apps/web/src/middleware.ts index 81410fce..93b23113 100644 --- a/apps/web/src/middleware.ts +++ b/apps/web/src/middleware.ts @@ -2,7 +2,7 @@ import { NextRequest } from 'next/server'; import withAuth from 'next-auth/middleware'; import createMiddleware from 'next-intl/middleware'; -import { routing } from './i18n/routing'; +import { routing } from './shared/i18n/routing'; const publicPages = ['/', '/auth', '/event/HALLOWEEN_2024', '/event/CHRISTMAS_2024', '/test/ranking']; diff --git a/apps/web/src/apis/QueryClientProvider.tsx b/apps/web/src/shared/api/QueryClientProvider.tsx similarity index 88% rename from apps/web/src/apis/QueryClientProvider.tsx rename to apps/web/src/shared/api/QueryClientProvider.tsx index a6214227..fef4d746 100644 --- a/apps/web/src/apis/QueryClientProvider.tsx +++ b/apps/web/src/shared/api/QueryClientProvider.tsx @@ -4,7 +4,7 @@ import { type PropsWithChildren } from 'react'; import { QueryClientProvider as BaseQueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; -import { getQueryClient } from '@/lib/react-query/queryClient'; +import { getQueryClient } from '@/shared/lib/react-query/queryClient'; const QueryClientProvider = ({ children }: PropsWithChildren) => { const queryClient = getQueryClient(); diff --git a/apps/web/src/auth.ts b/apps/web/src/shared/api/auth.ts similarity index 100% rename from apps/web/src/auth.ts rename to apps/web/src/shared/api/auth.ts diff --git a/apps/web/src/apis/interceptor.ts b/apps/web/src/shared/api/interceptor.ts similarity index 95% rename from apps/web/src/apis/interceptor.ts rename to apps/web/src/shared/api/interceptor.ts index 61fbb427..749e1ae7 100644 --- a/apps/web/src/apis/interceptor.ts +++ b/apps/web/src/shared/api/interceptor.ts @@ -2,8 +2,8 @@ import { getSession, signOut } from 'next-auth/react'; import { CustomException } from '@gitanimals/exception'; import type { AxiosError, AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from 'axios'; -import { getServerAuth } from '@/auth'; -import type { ApiErrorScheme } from '@/exceptions/type'; +import { getServerAuth } from '@/shared/api/auth'; +import type { ApiErrorScheme } from '@/shared/exceptions/type'; interface CachedSession { accessToken: string; diff --git a/apps/web/src/apis/index.ts b/apps/web/src/shared/api/legacyClient.ts similarity index 94% rename from apps/web/src/apis/index.ts rename to apps/web/src/shared/api/legacyClient.ts index 5020fdf1..974c97d8 100644 --- a/apps/web/src/apis/index.ts +++ b/apps/web/src/shared/api/legacyClient.ts @@ -1,6 +1,6 @@ import axios from 'axios'; -import { setInterceptors } from './interceptor'; +import { setInterceptors } from '@/shared/api/interceptor'; const API_URL = 'https://api.gitanimals.org'; diff --git a/apps/web/src/apis/render.ts b/apps/web/src/shared/api/render.ts similarity index 92% rename from apps/web/src/apis/render.ts rename to apps/web/src/shared/api/render.ts index 6dda72dc..54f21d60 100644 --- a/apps/web/src/apis/render.ts +++ b/apps/web/src/shared/api/render.ts @@ -1,6 +1,6 @@ import axios from 'axios'; -import { setInterceptors } from './interceptor'; +import { setInterceptors } from '@/shared/api/interceptor'; const API_URL = 'https://render.gitanimals.org'; diff --git a/apps/web/src/apis/slack/sendMessage.ts b/apps/web/src/shared/api/slack.ts similarity index 100% rename from apps/web/src/apis/slack/sendMessage.ts rename to apps/web/src/shared/api/slack.ts diff --git a/apps/web/src/assets/.gitkeep b/apps/web/src/shared/assets/.gitkeep similarity index 100% rename from apps/web/src/assets/.gitkeep rename to apps/web/src/shared/assets/.gitkeep diff --git a/apps/web/src/assets/lottie/congratulations.json b/apps/web/src/shared/assets/lottie/congratulations.json similarity index 100% rename from apps/web/src/assets/lottie/congratulations.json rename to apps/web/src/shared/assets/lottie/congratulations.json diff --git a/apps/web/src/constants/.gitkeep b/apps/web/src/shared/config/.gitkeep similarity index 100% rename from apps/web/src/constants/.gitkeep rename to apps/web/src/shared/config/.gitkeep diff --git a/apps/web/src/components/AnimalCard/AnimalCard.constant.ts b/apps/web/src/shared/config/animalTier.ts similarity index 100% rename from apps/web/src/components/AnimalCard/AnimalCard.constant.ts rename to apps/web/src/shared/config/animalTier.ts diff --git a/apps/web/src/constants/config.ts b/apps/web/src/shared/config/config.ts similarity index 100% rename from apps/web/src/constants/config.ts rename to apps/web/src/shared/config/config.ts diff --git a/apps/web/src/constants/coupon.ts b/apps/web/src/shared/config/coupon.ts similarity index 100% rename from apps/web/src/constants/coupon.ts rename to apps/web/src/shared/config/coupon.ts diff --git a/apps/web/src/constants/env.ts b/apps/web/src/shared/config/env.ts similarity index 100% rename from apps/web/src/constants/env.ts rename to apps/web/src/shared/config/env.ts diff --git a/apps/web/src/constants/github.ts b/apps/web/src/shared/config/github.ts similarity index 100% rename from apps/web/src/constants/github.ts rename to apps/web/src/shared/config/github.ts diff --git a/apps/web/src/constants/oauth.ts b/apps/web/src/shared/config/oauth.ts similarity index 100% rename from apps/web/src/constants/oauth.ts rename to apps/web/src/shared/config/oauth.ts diff --git a/apps/web/src/constants/outlink.ts b/apps/web/src/shared/config/outlink.ts similarity index 100% rename from apps/web/src/constants/outlink.ts rename to apps/web/src/shared/config/outlink.ts diff --git a/apps/web/src/constants/pet.ts b/apps/web/src/shared/config/pet.ts similarity index 100% rename from apps/web/src/constants/pet.ts rename to apps/web/src/shared/config/pet.ts diff --git a/apps/web/src/constants/route.ts b/apps/web/src/shared/config/route.ts similarity index 100% rename from apps/web/src/constants/route.ts rename to apps/web/src/shared/config/route.ts diff --git a/apps/web/src/constants/storage.ts b/apps/web/src/shared/config/storage.ts similarity index 100% rename from apps/web/src/constants/storage.ts rename to apps/web/src/shared/config/storage.ts diff --git a/apps/web/src/constants/time.ts b/apps/web/src/shared/config/time.ts similarity index 100% rename from apps/web/src/constants/time.ts rename to apps/web/src/shared/config/time.ts diff --git a/apps/web/src/exceptions/type.ts b/apps/web/src/shared/exceptions/type.ts similarity index 100% rename from apps/web/src/exceptions/type.ts rename to apps/web/src/shared/exceptions/type.ts diff --git a/apps/web/src/hooks/lifeCycle/useEffectOnce.ts b/apps/web/src/shared/hooks/lifeCycle/useEffectOnce.ts similarity index 100% rename from apps/web/src/hooks/lifeCycle/useEffectOnce.ts rename to apps/web/src/shared/hooks/lifeCycle/useEffectOnce.ts diff --git a/apps/web/src/hooks/useCopyToClipboard.ts b/apps/web/src/shared/hooks/useCopyToClipboard.ts similarity index 100% rename from apps/web/src/hooks/useCopyToClipboard.ts rename to apps/web/src/shared/hooks/useCopyToClipboard.ts diff --git a/apps/web/src/hooks/useDeviceInfo.tsx b/apps/web/src/shared/hooks/useDeviceInfo.tsx similarity index 100% rename from apps/web/src/hooks/useDeviceInfo.tsx rename to apps/web/src/shared/hooks/useDeviceInfo.tsx diff --git a/apps/web/src/hooks/useGetNewUrl.tsx b/apps/web/src/shared/hooks/useGetNewUrl.tsx similarity index 90% rename from apps/web/src/hooks/useGetNewUrl.tsx rename to apps/web/src/shared/hooks/useGetNewUrl.tsx index 10a20650..675d37a1 100644 --- a/apps/web/src/hooks/useGetNewUrl.tsx +++ b/apps/web/src/shared/hooks/useGetNewUrl.tsx @@ -3,7 +3,7 @@ import { useSearchParams } from 'next/navigation'; import { getNewUrl } from '@gitanimals/util-common'; -import { usePathname } from '@/i18n/routing'; +import { usePathname } from '@/shared/i18n/routing'; export const useGetNextUrl = () => { const pathname = usePathname(); diff --git a/apps/web/src/hooks/useIsMounted.ts b/apps/web/src/shared/hooks/useIsMounted.ts similarity index 100% rename from apps/web/src/hooks/useIsMounted.ts rename to apps/web/src/shared/hooks/useIsMounted.ts diff --git a/apps/web/src/hooks/useOutsideClick.ts b/apps/web/src/shared/hooks/useOutsideClick.ts similarity index 100% rename from apps/web/src/hooks/useOutsideClick.ts rename to apps/web/src/shared/hooks/useOutsideClick.ts diff --git a/apps/web/src/hooks/usePagination.ts b/apps/web/src/shared/hooks/usePagination.ts similarity index 100% rename from apps/web/src/hooks/usePagination.ts rename to apps/web/src/shared/hooks/usePagination.ts diff --git a/apps/web/src/hooks/useTimer.tsx b/apps/web/src/shared/hooks/useTimer.tsx similarity index 100% rename from apps/web/src/hooks/useTimer.tsx rename to apps/web/src/shared/hooks/useTimer.tsx diff --git a/apps/web/src/i18n/request.ts b/apps/web/src/shared/i18n/request.ts similarity index 85% rename from apps/web/src/i18n/request.ts rename to apps/web/src/shared/i18n/request.ts index 3b4f14c3..b3dc64fb 100644 --- a/apps/web/src/i18n/request.ts +++ b/apps/web/src/shared/i18n/request.ts @@ -1,7 +1,7 @@ import { notFound } from 'next/navigation'; import { getRequestConfig } from 'next-intl/server'; -import { sendLog } from '@/utils/log'; +import { sendLog } from '@/shared/utils/log'; import { routing } from './routing'; @@ -11,7 +11,7 @@ export default getRequestConfig(async ({ locale }) => { try { // Attempt to dynamically import the locale messages - const messages = (await import(`../../messages/${locale}.json`)).default; + const messages = (await import(`../../../messages/${locale}.json`)).default; return { messages }; } catch (error) { sendLog({ locale: locale, error: 'Error loading messages for locale' }, 'Error loading messages for locale'); diff --git a/apps/web/src/i18n/routing.ts b/apps/web/src/shared/i18n/routing.ts similarity index 100% rename from apps/web/src/i18n/routing.ts rename to apps/web/src/shared/i18n/routing.ts diff --git a/apps/web/src/i18n/useToggleLocale.ts b/apps/web/src/shared/i18n/useToggleLocale.ts similarity index 100% rename from apps/web/src/i18n/useToggleLocale.ts rename to apps/web/src/shared/i18n/useToggleLocale.ts diff --git a/apps/web/src/lib/analytics.ts b/apps/web/src/shared/lib/analytics.ts similarity index 92% rename from apps/web/src/lib/analytics.ts rename to apps/web/src/shared/lib/analytics.ts index b7427f73..83801bb8 100644 --- a/apps/web/src/lib/analytics.ts +++ b/apps/web/src/shared/lib/analytics.ts @@ -1,7 +1,7 @@ import mixpanel from 'mixpanel-browser'; -import { config } from '@/constants/config'; -import { isProd } from '@/constants/env'; +import { config } from '@/shared/config/config'; +import { isProd } from '@/shared/config/env'; export const initAnalytics = () => { if (!isProd) return; diff --git a/apps/web/src/lib/devtools/constants.ts b/apps/web/src/shared/lib/devtools/constants.ts similarity index 100% rename from apps/web/src/lib/devtools/constants.ts rename to apps/web/src/shared/lib/devtools/constants.ts diff --git a/apps/web/src/lib/devtools/devtools.ts b/apps/web/src/shared/lib/devtools/devtools.ts similarity index 100% rename from apps/web/src/lib/devtools/devtools.ts rename to apps/web/src/shared/lib/devtools/devtools.ts diff --git a/apps/web/src/lib/react-query/queryClient.ts b/apps/web/src/shared/lib/react-query/queryClient.ts similarity index 100% rename from apps/web/src/lib/react-query/queryClient.ts rename to apps/web/src/shared/lib/react-query/queryClient.ts diff --git a/apps/web/src/lib/supabase/client.ts b/apps/web/src/shared/lib/supabase/client.ts similarity index 100% rename from apps/web/src/lib/supabase/client.ts rename to apps/web/src/shared/lib/supabase/client.ts diff --git a/apps/web/src/lib/supabase/types.ts b/apps/web/src/shared/lib/supabase/types.ts similarity index 100% rename from apps/web/src/lib/supabase/types.ts rename to apps/web/src/shared/lib/supabase/types.ts diff --git a/apps/web/src/schema/action.d.ts b/apps/web/src/shared/schema/action.d.ts similarity index 100% rename from apps/web/src/schema/action.d.ts rename to apps/web/src/shared/schema/action.d.ts diff --git a/apps/web/src/schema/pagination.d.ts b/apps/web/src/shared/schema/pagination.d.ts similarity index 100% rename from apps/web/src/schema/pagination.d.ts rename to apps/web/src/shared/schema/pagination.d.ts diff --git a/apps/web/src/schema/user.d.ts b/apps/web/src/shared/schema/user.d.ts similarity index 100% rename from apps/web/src/schema/user.d.ts rename to apps/web/src/shared/schema/user.d.ts diff --git a/apps/web/src/store/loading.ts b/apps/web/src/shared/store/loading.ts similarity index 100% rename from apps/web/src/store/loading.ts rename to apps/web/src/shared/store/loading.ts diff --git a/apps/web/src/store/snackBar.ts b/apps/web/src/shared/store/snackBar.ts similarity index 100% rename from apps/web/src/store/snackBar.ts rename to apps/web/src/shared/store/snackBar.ts diff --git a/apps/web/src/styles/prevTextToken.ts b/apps/web/src/shared/styles/prevTextToken.ts similarity index 100% rename from apps/web/src/styles/prevTextToken.ts rename to apps/web/src/shared/styles/prevTextToken.ts diff --git a/apps/web/src/styles/scrollStyle.ts b/apps/web/src/shared/styles/scrollStyle.ts similarity index 100% rename from apps/web/src/styles/scrollStyle.ts rename to apps/web/src/shared/styles/scrollStyle.ts diff --git a/apps/web/src/components/Gitanimals.tsx b/apps/web/src/shared/ui/Gitanimals.tsx similarity index 97% rename from apps/web/src/components/Gitanimals.tsx rename to apps/web/src/shared/ui/Gitanimals.tsx index 4fc1a670..53a2250a 100644 --- a/apps/web/src/components/Gitanimals.tsx +++ b/apps/web/src/shared/ui/Gitanimals.tsx @@ -4,7 +4,7 @@ import { cn } from '@gitanimals/ui-tailwind'; -import { useClientUser } from '@/utils/clientAuth'; +import { useClientUser } from '@/shared/utils/clientAuth'; const GITANIMALS_URL = 'https://www.gitanimals.org/en_US'; diff --git a/apps/web/src/utils/animals.ts b/apps/web/src/shared/utils/animals.ts similarity index 83% rename from apps/web/src/utils/animals.ts rename to apps/web/src/shared/utils/animals.ts index bd2807c0..0f8bf406 100644 --- a/apps/web/src/utils/animals.ts +++ b/apps/web/src/shared/utils/animals.ts @@ -1,5 +1,5 @@ -import type { AnimalTierType } from '@/components/AnimalCard/AnimalCard.constant'; -import { ANIMAL_TIER_INFO, AnimalTier } from '@/components/AnimalCard/AnimalCard.constant'; +import type { AnimalTierType } from '@/shared/config/animalTier'; +import { ANIMAL_TIER_INFO, AnimalTier } from '@/shared/config/animalTier'; /** * 페르소나 등급 정렬 우선순위 (낮을수록 우선) diff --git a/apps/web/src/utils/clientAuth.ts b/apps/web/src/shared/utils/clientAuth.ts similarity index 100% rename from apps/web/src/utils/clientAuth.ts rename to apps/web/src/shared/utils/clientAuth.ts diff --git a/apps/web/src/utils/common.ts b/apps/web/src/shared/utils/common.ts similarity index 100% rename from apps/web/src/utils/common.ts rename to apps/web/src/shared/utils/common.ts diff --git a/apps/web/src/utils/copy.ts b/apps/web/src/shared/utils/copy.ts similarity index 100% rename from apps/web/src/utils/copy.ts rename to apps/web/src/shared/utils/copy.ts diff --git a/apps/web/src/utils/dev.ts b/apps/web/src/shared/utils/dev.ts similarity index 65% rename from apps/web/src/utils/dev.ts rename to apps/web/src/shared/utils/dev.ts index e401d8d0..94fd4df3 100644 --- a/apps/web/src/utils/dev.ts +++ b/apps/web/src/shared/utils/dev.ts @@ -1,4 +1,4 @@ -import { SERVICE_MAINTAINER } from '@/constants/github'; +import { SERVICE_MAINTAINER } from '@/shared/config/github'; export const checkIdDevAccessPossible = (username: string) => { return SERVICE_MAINTAINER.includes(username); diff --git a/apps/web/src/utils/generateId.ts b/apps/web/src/shared/utils/generateId.ts similarity index 100% rename from apps/web/src/utils/generateId.ts rename to apps/web/src/shared/utils/generateId.ts diff --git a/apps/web/src/utils/image.ts b/apps/web/src/shared/utils/image.ts similarity index 75% rename from apps/web/src/utils/image.ts rename to apps/web/src/shared/utils/image.ts index 54f2e0fc..e007db12 100644 --- a/apps/web/src/utils/image.ts +++ b/apps/web/src/shared/utils/image.ts @@ -1,4 +1,4 @@ -import { STATIC_IMAGE_URL } from '@/constants/outlink'; +import { STATIC_IMAGE_URL } from '@/shared/config/outlink'; export const getPersonaImage = (type: string) => `${STATIC_IMAGE_URL}/personas/${type}`; diff --git a/apps/web/src/utils/list.ts b/apps/web/src/shared/utils/list.ts similarity index 100% rename from apps/web/src/utils/list.ts rename to apps/web/src/shared/utils/list.ts diff --git a/apps/web/src/utils/log.ts b/apps/web/src/shared/utils/log.ts similarity index 100% rename from apps/web/src/utils/log.ts rename to apps/web/src/shared/utils/log.ts diff --git a/apps/web/src/utils/number.ts b/apps/web/src/shared/utils/number.ts similarity index 100% rename from apps/web/src/utils/number.ts rename to apps/web/src/shared/utils/number.ts diff --git a/apps/web/src/utils/path.ts b/apps/web/src/shared/utils/path.ts similarity index 100% rename from apps/web/src/utils/path.ts rename to apps/web/src/shared/utils/path.ts diff --git a/apps/web/src/utils/string.ts b/apps/web/src/shared/utils/string.ts similarity index 100% rename from apps/web/src/utils/string.ts rename to apps/web/src/shared/utils/string.ts diff --git a/apps/web/src/components/GNB/DesktopGNB.tsx b/apps/web/src/widgets/gnb/DesktopGNB.tsx similarity index 92% rename from apps/web/src/components/GNB/DesktopGNB.tsx rename to apps/web/src/widgets/gnb/DesktopGNB.tsx index ec59601c..7ba6444a 100644 --- a/apps/web/src/components/GNB/DesktopGNB.tsx +++ b/apps/web/src/widgets/gnb/DesktopGNB.tsx @@ -5,13 +5,13 @@ import { GithubIcon } from '@gitanimals/ui-icon'; import { cn } from '@gitanimals/ui-tailwind/utils'; import { ChevronRightIcon } from 'lucide-react'; -import { getServerAuth } from '@/auth'; +import { getServerAuth } from '@/shared/api/auth'; import { AdaptiveLink } from '@/components/AdaptiveLink'; -import { GIT_ANIMALS_MAIN_URL } from '@/constants/outlink'; -import { Link } from '@/i18n/routing'; -import { checkIdDevAccessPossible } from '@/utils/dev'; +import { GIT_ANIMALS_MAIN_URL } from '@/shared/config/outlink'; +import { Link } from '@/shared/i18n/routing'; +import { checkIdDevAccessPossible } from '@/shared/utils/dev'; -import { LoginButton, LogoutButton } from '../AuthButton'; +import { LoginButton, LogoutButton } from '@/components/AuthButton'; import { Notification } from './Notification/Notification'; import { DesktopLanguageSelector } from './LanguageSelector'; diff --git a/apps/web/src/components/GNB/GNB.tsx b/apps/web/src/widgets/gnb/GNB.tsx similarity index 79% rename from apps/web/src/components/GNB/GNB.tsx rename to apps/web/src/widgets/gnb/GNB.tsx index 94e61420..fa6298d9 100644 --- a/apps/web/src/components/GNB/GNB.tsx +++ b/apps/web/src/widgets/gnb/GNB.tsx @@ -1,4 +1,4 @@ -import { MediaQuery } from '../MediaQuery'; +import { MediaQuery } from '@/components/MediaQuery'; import { DesktopGNB } from './DesktopGNB'; import { MobileGNB } from './MobileGNB'; diff --git a/apps/web/src/components/GNB/LanguageSelector.tsx b/apps/web/src/widgets/gnb/LanguageSelector.tsx similarity index 97% rename from apps/web/src/components/GNB/LanguageSelector.tsx rename to apps/web/src/widgets/gnb/LanguageSelector.tsx index 44285e79..aa875dd2 100644 --- a/apps/web/src/components/GNB/LanguageSelector.tsx +++ b/apps/web/src/widgets/gnb/LanguageSelector.tsx @@ -7,7 +7,7 @@ import { cn } from '@gitanimals/ui-tailwind'; import { AnimatePresence, motion } from 'framer-motion'; import { ChevronLeft, Globe } from 'lucide-react'; -import { Link, type Locale, usePathname } from '@/i18n/routing'; +import { Link, type Locale, usePathname } from '@/shared/i18n/routing'; const LOCALE_MAP: Record = { en_US: 'English', diff --git a/apps/web/src/components/GNB/MobileGNB.tsx b/apps/web/src/widgets/gnb/MobileGNB.tsx similarity index 97% rename from apps/web/src/components/GNB/MobileGNB.tsx rename to apps/web/src/widgets/gnb/MobileGNB.tsx index d25cb2f5..07008827 100644 --- a/apps/web/src/components/GNB/MobileGNB.tsx +++ b/apps/web/src/widgets/gnb/MobileGNB.tsx @@ -12,8 +12,8 @@ import { ChevronRight, Globe, LogOutIcon, Menu } from 'lucide-react'; import { AdaptiveLink } from '@/components/AdaptiveLink'; import { RenderLoginButton, RenderLogoutButton } from '@/components/AuthButton'; -import { Link } from '@/i18n/routing'; -import { useClientSession } from '@/utils/clientAuth'; +import { Link } from '@/shared/i18n/routing'; +import { useClientSession } from '@/shared/utils/clientAuth'; import { MobileLanguageSelector } from './LanguageSelector'; import { LOGIN_NAV_MENU_LIST, NON_LOGIN_NAV_MENU_LIST } from './menu.constants'; diff --git a/apps/web/src/components/GNB/Notification/InboxList.tsx b/apps/web/src/widgets/gnb/Notification/InboxList.tsx similarity index 94% rename from apps/web/src/components/GNB/Notification/InboxList.tsx rename to apps/web/src/widgets/gnb/Notification/InboxList.tsx index 75db0269..6e48faae 100644 --- a/apps/web/src/components/GNB/Notification/InboxList.tsx +++ b/apps/web/src/widgets/gnb/Notification/InboxList.tsx @@ -5,10 +5,10 @@ import { inboxQueries, useReadInbox } from '@gitanimals/react-query'; import { cn } from '@gitanimals/ui-tailwind/utils'; import { useQueryClient } from '@tanstack/react-query'; -import { Link } from '@/i18n/routing'; -import { customScrollStyle } from '@/styles/scrollStyle'; +import { Link } from '@/shared/i18n/routing'; +import { customScrollStyle } from '@/shared/styles/scrollStyle'; -import { AnimatePortal } from '../../Portal'; +import { AnimatePortal } from '@/components/Portal'; export const InboxList = ({ isOpen, list }: { isOpen: boolean; list: Inbox[] }) => { const t = useTranslations('Layout'); diff --git a/apps/web/src/components/GNB/Notification/Notification.tsx b/apps/web/src/widgets/gnb/Notification/Notification.tsx similarity index 100% rename from apps/web/src/components/GNB/Notification/Notification.tsx rename to apps/web/src/widgets/gnb/Notification/Notification.tsx diff --git a/apps/web/src/components/GNB/Notification/notification.contants.ts b/apps/web/src/widgets/gnb/Notification/notification.contants.ts similarity index 100% rename from apps/web/src/components/GNB/Notification/notification.contants.ts rename to apps/web/src/widgets/gnb/Notification/notification.contants.ts diff --git a/apps/web/src/widgets/gnb/index.ts b/apps/web/src/widgets/gnb/index.ts new file mode 100644 index 00000000..04a7d338 --- /dev/null +++ b/apps/web/src/widgets/gnb/index.ts @@ -0,0 +1 @@ +export { default as GNB } from './GNB'; diff --git a/apps/web/src/components/GNB/menu.constants.tsx b/apps/web/src/widgets/gnb/menu.constants.tsx similarity index 100% rename from apps/web/src/components/GNB/menu.constants.tsx rename to apps/web/src/widgets/gnb/menu.constants.tsx diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index 0b49b237..1fa23fd1 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -4,9 +4,6 @@ "paths": { "@/*": [ "./src/*" - ], - "_panda/*": [ - "./styled-system/*" ] }, "plugins": [ @@ -17,9 +14,6 @@ "allowJs": true, "forceConsistentCasingInFileNames": false, "strictNullChecks": true, - "types": [ - "@pandacss/dev" - ], "target": "ES2017" }, "include": [ @@ -27,7 +21,6 @@ "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", - "styled-system", "types/**/*.ts" ], "exclude": [ diff --git a/packages/ui/tailwind/package.json b/packages/ui/tailwind/package.json index e91fdaeb..e02267b4 100644 --- a/packages/ui/tailwind/package.json +++ b/packages/ui/tailwind/package.json @@ -47,7 +47,6 @@ }, "dependencies": { "@gitanimals/ui-token": "workspace:*", - "@react-spring/web": "^9.7.5", "@radix-ui/react-accordion": "^1.2.1", "@radix-ui/react-checkbox": "^1.1.1", "@radix-ui/react-dialog": "^1.1.2", @@ -56,6 +55,7 @@ "@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-tooltip": "^1.1.6", + "@react-spring/web": "^9.7.5", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.408.0", @@ -75,6 +75,7 @@ "@types/react": "^18.2.61", "@types/react-dom": "^18.3.7", "autoprefixer": "^10.4.20", + "chromatic": "^16.0.0", "postcss": "^8.4.49", "react": "^18.2.0", "react-dom": "^18.3.1", diff --git a/packages/ui/tailwind/src/components/ui/alert-dialog.stories.tsx b/packages/ui/tailwind/src/components/ui/alert-dialog.stories.tsx new file mode 100644 index 00000000..f7cc92d9 --- /dev/null +++ b/packages/ui/tailwind/src/components/ui/alert-dialog.stories.tsx @@ -0,0 +1,54 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { AlertDialog } from './alert-dialog'; + +const meta: Meta = { + title: 'UI/AlertDialog', + component: AlertDialog, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + args: { + isOpen: true, + onClose: () => {}, + }, + argTypes: { + title: { control: 'text' }, + description: { control: 'text' }, + closeText: { control: 'text' }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + title: 'Notice', + description: 'Your pet has leveled up!', + closeText: 'OK', + }, +}; + +export const ErrorAlert: Story = { + args: { + title: 'Error', + description: 'Failed to complete the action. Please try again later.', + closeText: 'Close', + }, +}; + +export const WithChildren: Story = { + args: { + title: 'Monthly Summary', + description: 'Here is your activity for this month:', + closeText: 'Got it', + children: ( +
    +
  • Contribution streak: 30 days
  • +
  • Pets earned: 3
  • +
  • Total points: 1,500
  • +
+ ), + }, +}; diff --git a/packages/ui/tailwind/src/components/ui/alert-dialog.tsx b/packages/ui/tailwind/src/components/ui/alert-dialog.tsx new file mode 100644 index 00000000..779c6a2c --- /dev/null +++ b/packages/ui/tailwind/src/components/ui/alert-dialog.tsx @@ -0,0 +1,37 @@ +'use client'; + +import type { ReactNode } from 'react'; +import { Dialog } from './dialog'; +import { Button } from './button'; + +export interface AlertDialogProps { + isOpen: boolean; + onClose: () => void; + title: string; + description?: ReactNode; + closeText?: string; + children?: ReactNode; +} + +export function AlertDialog({ isOpen, onClose, title, description, closeText = 'Close', children }: AlertDialogProps) { + return ( + + + + {title} + + {description && ( + + {description} + + )} +
{children}
+
+ +
+
+
+ ); +} diff --git a/packages/ui/tailwind/src/components/ui/button.tsx b/packages/ui/tailwind/src/components/ui/button.tsx index a82bc782..4ffecc52 100644 --- a/packages/ui/tailwind/src/components/ui/button.tsx +++ b/packages/ui/tailwind/src/components/ui/button.tsx @@ -9,6 +9,7 @@ const buttonVariants = cva( 'font-product text-glyph-16 font-normal', 'transition-all duration-200', 'disabled:cursor-not-allowed', + 'py-[2px] h-fit', ].join(' '), { variants: { @@ -31,9 +32,9 @@ const buttonVariants = cva( ].join(' '), }, size: { - s: 'px-6 h-8 min-h-[32px] text-glyph-14', - m: 'px-[30px] h-10 min-h-[40px] text-glyph-16', - l: 'px-[76px] py-[25px] min-h-[76px] text-glyph-20', + s: 'px-2 min-w-[64px] min-h-[32px] text-glyph-14', + m: 'px-3 min-w-[120px] min-h-[40px] text-glyph-16', + l: 'px-4 min-w-[176px] py-[25px] min-h-[76px] text-glyph-20', }, floating: { true: 'w-full max-w-[calc(100%-32px)] fixed bottom-4 left-1/2 -translate-x-1/2', @@ -45,7 +46,7 @@ const buttonVariants = cva( size: 'm', floating: false, }, - } + }, ); export interface ButtonProps @@ -56,14 +57,8 @@ export interface ButtonProps const Button = React.forwardRef( ({ className, variant, size, floating, ...props }, ref) => { - return ( -
+ +
+ + + ); +} diff --git a/packages/ui/tailwind/src/components/ui/dialog.stories.tsx b/packages/ui/tailwind/src/components/ui/dialog.stories.tsx deleted file mode 100644 index f2a9321a..00000000 --- a/packages/ui/tailwind/src/components/ui/dialog.stories.tsx +++ /dev/null @@ -1,165 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; -import { - Dialog, - DialogTrigger, - DialogContent, - DialogHeader, - DialogFooter, - DialogTitle, - DialogDescription, -} from './dialog'; -import { Button } from './button'; - -const meta: Meta = { - title: 'UI/Dialog', - component: DialogContent, - parameters: { - layout: 'centered', - }, - tags: ['autodocs'], - argTypes: { - size: { - control: 'select', - options: ['default', 'large', 'screen'], - description: 'Dialog size variant', - }, - isShowClose: { - control: 'boolean', - description: 'Show close button', - }, - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - render: (args) => ( - - - - - - Dialog Title - - This is a dialog description. It provides additional context about the dialog content. - - - - - ), - args: { - size: 'default', - isShowClose: true, - }, -}; - -export const WithoutCloseButton: Story = { - render: (args) => ( - - - - - - No Close Button - - This dialog does not have a close button in the corner. - - - - - ), - args: { - size: 'default', - isShowClose: false, - }, -}; - -export const Large: Story = { - render: (args) => ( - - - - - - - Large Dialog - - This is a large dialog that takes up more screen space. - It's useful for displaying more complex content or forms. - - -
-

- Large dialogs are great for: -

-
    -
  • Complex forms
  • -
  • Detailed information
  • -
  • Multi-step processes
  • -
  • Preview content
  • -
-
- - - - -
-
- ), - args: { - size: 'large', - isShowClose: true, - }, -}; - -export const CompoundComponent: Story = { - render: () => ( - - - - - - Compound Component Pattern - - The Dialog component supports a compound component pattern using dot notation. - - - - - - - - - ), -}; - -export const ConfirmationDialog: Story = { - render: () => ( - - - - - - Delete Item? - - Are you sure you want to delete this item? This action cannot be undone. - -
- - - - -
-
-
- ), -}; diff --git a/packages/ui/tailwind/src/components/ui/dialog.tsx b/packages/ui/tailwind/src/components/ui/dialog.tsx index fdc2c71c..32edf2f2 100644 --- a/packages/ui/tailwind/src/components/ui/dialog.tsx +++ b/packages/ui/tailwind/src/components/ui/dialog.tsx @@ -43,7 +43,7 @@ const dialogContentVariants = cva( large: [ 'p-[60px_40px] rounded-2xl bg-gray-150', 'max-w-[calc(100%-400px)] max-h-[calc(100%-240px)] w-full h-full', - 'max-[1200px]:p-[48px_24px] max-[1200px]:max-w-[calc(100vw-240px)] max-[1200px]:max-h-[calc(100vh-120px)]', + 'max-1200:p-[48px_24px] max-1200:max-w-[calc(100vw-240px)] max-1200:max-h-[calc(100vh-120px)]', 'max-mobile:h-full max-mobile:max-w-[100vw] max-mobile:max-h-[100vh] max-mobile:rounded-none', ].join(' '), screen: [ @@ -106,8 +106,8 @@ const DialogTitle = React.forwardRef< (({ className, ...props }, ref) => ( )); diff --git a/packages/ui/tailwind/src/components/ui/index.ts b/packages/ui/tailwind/src/components/ui/index.ts index 916d0aed..5c0ff991 100644 --- a/packages/ui/tailwind/src/components/ui/index.ts +++ b/packages/ui/tailwind/src/components/ui/index.ts @@ -1,6 +1,8 @@ export * from './accordion'; +export * from './alert-dialog'; export * from './button'; export * from './checkbox'; +export * from './confirm-dialog'; export * from './dialog'; export * from './dropdown-menu'; export * from './label'; diff --git a/packages/ui/tailwind/src/theme/screens.ts b/packages/ui/tailwind/src/theme/screens.ts index 6d978d3a..5d97f059 100644 --- a/packages/ui/tailwind/src/theme/screens.ts +++ b/packages/ui/tailwind/src/theme/screens.ts @@ -34,10 +34,17 @@ export const screens = { xl: '1280px', '2xl': '1440px', - // Max-width breakpoints (matching PandaCSS conditions) - 'max-mobile': { max: '768px' }, - 'max-tablet': { max: '1024px' }, + // Max-width breakpoints — DESCENDING order so cascade works correctly + // (larger max-width first, smaller last → smaller breakpoints override larger ones) 'max-pc': { max: '1440px' }, + 'max-1400': { max: '1400px' }, + 'max-1200': { max: '1200px' }, + 'max-tablet': { max: '1024px' }, + 'max-950': { max: '950px' }, + 'max-900': { max: '900px' }, + 'max-mobile': { max: '768px' }, + 'max-600': { max: '600px' }, + 'max-400': { max: '400px' }, // Min-width named breakpoints desktop: { min: '769px' }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 92fd1dbf..3db2372f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1066,6 +1066,9 @@ importers: autoprefixer: specifier: ^10.4.20 version: 10.4.23(postcss@8.5.6) + chromatic: + specifier: ^16.0.0 + version: 16.0.0 postcss: specifier: ^8.4.49 version: 8.5.6 @@ -7114,6 +7117,18 @@ packages: '@chromatic-com/playwright': optional: true + chromatic@16.0.0: + resolution: {integrity: sha512-O81RVGDXXoreNeG894hjaUx08xep+C/BA6aJNMZkwSjH7Lln8IweZekBpBEoQPNNpjmHyZvcTIwN/aGitdK53Q==} + hasBin: true + peerDependencies: + '@chromatic-com/cypress': ^0.*.* || ^1.0.0 + '@chromatic-com/playwright': ^0.*.* || ^1.0.0 + peerDependenciesMeta: + '@chromatic-com/cypress': + optional: true + '@chromatic-com/playwright': + optional: true + chrome-trace-event@1.0.4: resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} engines: {node: '>=6.0'} @@ -21340,6 +21355,8 @@ snapshots: chromatic@11.5.4: {} + chromatic@16.0.0: {} + chrome-trace-event@1.0.4: {} ci-info@3.9.0: {} @@ -26645,7 +26662,7 @@ snapshots: postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.6)(yaml@2.4.3): dependencies: - lilconfig: 3.1.3 + lilconfig: 3.1.1 optionalDependencies: jiti: 1.21.7 postcss: 8.5.6