From 485b041f970636566db9dbfb86c0833181005f6a Mon Sep 17 00:00:00 2001 From: bicochan Date: Thu, 26 Feb 2026 11:43:45 +0900 Subject: [PATCH] =?UTF-8?q?IIPBS=20=ED=94=84=EB=A1=9C=EC=A0=9D=ED=8A=B8=20?= =?UTF-8?q?=ED=85=9C=ED=94=8C=EB=A6=BF=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/(template)/.env.example | 5 + apps/(template)/.eslintrc.json | 3 + apps/(template)/.gitignore | 36 ++ apps/(template)/.prettierrc | 8 + apps/(template)/README.md | 248 ++++++++++++ .../app/(with-base-layout)/layout.tsx | 31 ++ .../app/(with-base-layout)/page.tsx | 9 + apps/(template)/app/README.md | 169 ++++++++ apps/(template)/app/globals.css | 15 + apps/(template)/app/layout.tsx | 30 ++ apps/(template)/app/providers.tsx | 18 + apps/(template)/entities/README.md | 114 ++++++ apps/(template)/entities/menu/index.ts | 1 + apps/(template)/entities/menu/model/types.ts | 19 + apps/(template)/eslint.config.mjs | 18 + apps/(template)/features/README.md | 130 +++++++ apps/(template)/next.config.ts | 8 + apps/(template)/package.json | 36 ++ apps/(template)/postcss.config.mjs | 8 + apps/(template)/shared/README.md | 149 +++++++ apps/(template)/shared/api/client.ts | 9 + apps/(template)/shared/api/index.ts | 1 + apps/(template)/shared/config/env.ts | 11 + apps/(template)/shared/config/index.ts | 1 + apps/(template)/shared/lib/dayjs.ts | 12 + apps/(template)/shared/lib/index.ts | 2 + apps/(template)/shared/lib/query-client.ts | 11 + apps/(template)/shared/model/index.ts | 29 ++ apps/(template)/shared/ui/index.ts | 1 + apps/(template)/shared/ui/page-layout.tsx | 35 ++ apps/(template)/tailwind.config.ts | 29 ++ apps/(template)/tsconfig.json | 35 ++ apps/(template)/widgets/README.md | 135 +++++++ pnpm-lock.yaml | 368 ++++++++++-------- 34 files changed, 1575 insertions(+), 159 deletions(-) create mode 100644 apps/(template)/.env.example create mode 100644 apps/(template)/.eslintrc.json create mode 100644 apps/(template)/.gitignore create mode 100644 apps/(template)/.prettierrc create mode 100644 apps/(template)/README.md create mode 100644 apps/(template)/app/(with-base-layout)/layout.tsx create mode 100644 apps/(template)/app/(with-base-layout)/page.tsx create mode 100644 apps/(template)/app/README.md create mode 100644 apps/(template)/app/globals.css create mode 100644 apps/(template)/app/layout.tsx create mode 100644 apps/(template)/app/providers.tsx create mode 100644 apps/(template)/entities/README.md create mode 100644 apps/(template)/entities/menu/index.ts create mode 100644 apps/(template)/entities/menu/model/types.ts create mode 100644 apps/(template)/eslint.config.mjs create mode 100644 apps/(template)/features/README.md create mode 100644 apps/(template)/next.config.ts create mode 100644 apps/(template)/package.json create mode 100644 apps/(template)/postcss.config.mjs create mode 100644 apps/(template)/shared/README.md create mode 100644 apps/(template)/shared/api/client.ts create mode 100644 apps/(template)/shared/api/index.ts create mode 100644 apps/(template)/shared/config/env.ts create mode 100644 apps/(template)/shared/config/index.ts create mode 100644 apps/(template)/shared/lib/dayjs.ts create mode 100644 apps/(template)/shared/lib/index.ts create mode 100644 apps/(template)/shared/lib/query-client.ts create mode 100644 apps/(template)/shared/model/index.ts create mode 100644 apps/(template)/shared/ui/index.ts create mode 100644 apps/(template)/shared/ui/page-layout.tsx create mode 100644 apps/(template)/tailwind.config.ts create mode 100644 apps/(template)/tsconfig.json create mode 100644 apps/(template)/widgets/README.md diff --git a/apps/(template)/.env.example b/apps/(template)/.env.example new file mode 100644 index 000000000..b2d706024 --- /dev/null +++ b/apps/(template)/.env.example @@ -0,0 +1,5 @@ +# apps/as/shared/config/env.ts 참고 +NEXT_PUBLIC_PORT= +NEXT_PUBLIC_PROXY_HOST= +NEXT_PUBLIC_SITE_ID= +NEXT_PUBLIC_SERVER_API_URL= diff --git a/apps/(template)/.eslintrc.json b/apps/(template)/.eslintrc.json new file mode 100644 index 000000000..ea782d70a --- /dev/null +++ b/apps/(template)/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/apps/(template)/.gitignore b/apps/(template)/.gitignore new file mode 100644 index 000000000..fd3dbb571 --- /dev/null +++ b/apps/(template)/.gitignore @@ -0,0 +1,36 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/apps/(template)/.prettierrc b/apps/(template)/.prettierrc new file mode 100644 index 000000000..059b4e33c --- /dev/null +++ b/apps/(template)/.prettierrc @@ -0,0 +1,8 @@ +{ + "semi": true, + "trailingComma": "all", + "singleQuote": false, + "printWidth": 100, + "tabWidth": 2, + "plugins": ["prettier-plugin-tailwindcss"] +} diff --git a/apps/(template)/README.md b/apps/(template)/README.md new file mode 100644 index 000000000..7e14528f8 --- /dev/null +++ b/apps/(template)/README.md @@ -0,0 +1,248 @@ +# AS Application + +Next.js 애플리케이션 with FSD(Feature Sliced Design) 아키텍처 + +## 기술 스택 + +- **프레임워크**: Next.js 15 + TypeScript +- **스타일링**: Tailwind CSS +- **상태관리**: Zustand +- **데이터 페칭**: TanStack Query (React Query) +- **폼 관리**: React Hook Form + Zod +- **테이블**: TanStack Table +- **HTTP 클라이언트**: Axios +- **날짜 처리**: Day.js + +## 프로젝트 구조 (FSD) + +이 프로젝트는 **Feature-Sliced Design v2.1**을 따르며, 계층적 아키텍처로 구성되어 있습니다. + +``` +as/ +├── app/ # App 레이어 (Next.js App Router) +│ ├── (routes)/ # 라우트 구조 +│ ├── layout.tsx # 루트 레이아웃 +│ ├── page.tsx # 홈 페이지 +│ ├── globals.css # 글로벌 스타일 +│ └── providers.tsx # 전역 providers +│ +├── widgets/ # Widgets 레이어 +│ └── [widget-name]/ # 큰 독립적 UI 블록 (여러 페이지에서 재사용) +│ ├── ui/ +│ ├── model/ +│ └── index.ts +│ +├── features/ # Features 레이어 +│ └── [feature-name]/ # 사용자 상호작용 기능 (여러 페이지에서 재사용) +│ ├── ui/ +│ ├── api/ +│ ├── model/ +│ └── index.ts +│ +├── entities/ # Entities 레이어 +│ └── [entity-name]/ # 비즈니스 엔티티 (User, Product 등) +│ ├── model/ +│ ├── api/ +│ ├── ui/ +│ └── index.ts +│ +└── shared/ # Shared 레이어 + ├── ui/ # UI Kit (비즈니스 로직 없는 공통 컴포넌트) + ├── api/ # API 클라이언트 설정 + ├── lib/ # 내부 라이브러리 (dates, colors 등) + ├── config/ # 환경 설정, feature flags + ├── types/ # 공통 타입 정의 + └── routes/ # 라우트 상수 +``` + +### 계층 구조 이해하기 + +FSD는 **3단계 계층**으로 구성됩니다: + +1. **Layers (레이어)**: 책임과 의존성에 따른 수평적 분리 (app, pages, widgets, features, entities, shared) +2. **Slices (슬라이스)**: 비즈니스 도메인에 따른 수직적 분리 (user, product, auth-login 등) +3. **Segments (세그먼트)**: 기술적 목적에 따른 분리 (ui, api, model, lib 등) + +> 📚 각 레이어의 자세한 설명은 해당 폴더의 README.md를 참고하세요. + +## FSD 레이어 설명 + +FSD는 계층적 아키텍처로, 각 레이어는 책임의 범위와 의존성의 정도에 따라 구분됩니다. 7개의 표준화된 레이어가 있으며, 프로젝트에 필요한 레이어만 사용하면 됩니다. + +### 레이어 계층 구조 (위에서 아래로) + +``` +app/ ↑ 가장 높은 책임, 가장 많은 의존성 +pages/ | +widgets/ | +features/ | +entities/ | +shared/ ↓ 가장 낮은 책임, 의존성 없음 +``` + +### 1. App +- **목적**: 앱 전체에 영향을 주는 기술적/비즈니스적 사항 +- **내용**: Context providers, 글로벌 스타일, 스토어 설정, 라우터 설정, 애널리틱스 +- **특징**: 슬라이스가 없고 세그먼트로만 구성 +- **의존성**: 모든 하위 레이어 사용 가능 + +### 2. Pages (app 디렉토리) +- **목적**: 웹사이트나 애플리케이션을 구성하는 페이지/화면 +- **내용**: 페이지 UI, 로딩 상태, 에러 바운더리, 데이터 fetching +- **특징**: 한 페이지당 한 슬라이스 (유사한 페이지는 그룹화 가능) +- **의존성**: `widgets`, `features`, `entities`, `shared` 사용 가능 + +> 💡 **FSD v2.1 권장사항**: "pages-first" 접근 방식을 따라, 재사용되지 않는 큰 UI 블록과 비즈니스 로직은 페이지 슬라이스에 유지하세요. + +### 3. Widgets +- **목적**: 큰 규모의 독립적인 UI 블록 +- **내용**: 여러 features와 entities를 조합한 복합 UI +- **사용 시기**: 여러 페이지에서 재사용되거나, 페이지에 여러 개의 큰 독립적 블록이 있을 때 +- **의존성**: `features`, `entities`, `shared` 사용 가능 (다른 `widgets` 불가) + +### 4. Features +- **목적**: 사용자가 수행하는 주요 상호작용과 비즈니스 기능 +- **내용**: 사용자 시나리오 (예: 로그인, 상품 검색, 댓글 작성) +- **사용 시기**: 여러 페이지에서 재사용되는 기능 +- **의존성**: `entities`, `shared` 사용 가능 (다른 `features` 불가) + +### 5. Entities +- **목적**: 비즈니스가 다루는 실제 개념 (User, Product, Order 등) +- **내용**: 데이터 모델, 타입, API 요청, 엔티티의 시각적 표현 +- **특징**: 비즈니스 용어로 명명 +- **의존성**: 다른 `entities`, `shared` 사용 가능 + +### 6. Shared +- **목적**: 애플리케이션의 기반 제공 +- **내용**: API 클라이언트, UI Kit, 유틸리티 라이브러리, 글로벌 설정 +- **특징**: 비즈니스 로직 없음, 슬라이스가 없고 세그먼트로만 구성 +- **의존성**: 외부 라이브러리만 사용 가능 (다른 레이어 불가) + +## 레이어 간 Import 규칙 + +> **핵심 규칙**: 슬라이스 내의 모듈(파일)은 **오직 하위 레이어**에 있는 다른 슬라이스만 import할 수 있습니다. + +예시: +- `features/auth-login/ui/form.tsx`는 `entities/user`와 `shared/ui`를 import 가능 +- 하지만 다른 `features/post-create`나 상위 레이어인 `widgets`는 import 불가 +- 같은 슬라이스 내부(`features/auth-login/`)의 다른 파일들은 자유롭게 import 가능 + +## Public API 규칙 + +> **슬라이스 외부의 모듈은 슬라이스의 Public API만 참조할 수 있으며, 내부 파일 구조에 직접 접근할 수 없습니다.** + +모든 슬라이스(및 슬라이스가 없는 레이어의 세그먼트)는 반드시 `index.ts`를 통해 Public API를 정의해야 합니다. + +```typescript +// ✅ 올바른 import +import { LoginForm } from 'features/auth-login'; + +// ❌ 잘못된 import (내부 구조 직접 접근) +import { LoginForm } from 'features/auth-login/ui/login-form'; +``` + +## 시작하기 + +```bash +# 의존성 설치 +pnpm install + +# 개발 서버 실행 +pnpm run dev + +# 프로덕션 빌드 +pnpm run build + +# 프로덕션 서버 실행 +pnpm start +``` + +## 환경 변수 + +`.env.local` 파일 생성: + +```env +NEXT_PUBLIC_API_URL=http://localhost:3000/api +``` + +## FSD 핵심 규칙 + +### 1. Import 규칙 + +> **슬라이스 내의 모듈(파일)은 오직 하위 레이어에 있는 슬라이스만 import할 수 있습니다.** + +```typescript +// ✅ 올바른 import (하위 레이어) +// features/auth-login/ui/form.tsx +import { User } from 'entities/user'; +import { Button } from 'shared/ui/button'; + +// ❌ 잘못된 import (같은 레이어) +// features/auth-login/ui/form.tsx +import { PostForm } from 'features/post-create'; // 금지! + +// ❌ 잘못된 import (상위 레이어) +// features/auth-login/ui/form.tsx +import { Header } from 'widgets/header'; // 금지! +``` + +### 2. Public API 규칙 + +> **슬라이스 외부에서는 Public API(index.ts)를 통해서만 접근해야 합니다.** + +```typescript +// ✅ 올바른 import (Public API 사용) +import { LoginForm } from 'features/auth-login'; + +// ❌ 잘못된 import (내부 구조 직접 접근) +import { LoginForm } from 'features/auth-login/ui/login-form'; +``` + +**Public API 작성 시 주의사항**: +- 와일드카드 re-export(`export * from`) 사용 지양 +- 필요한 것만 명시적으로 export +- 슬라이스의 인터페이스를 명확하게 표현 + +```typescript +// ❌ 나쁜 예시 +export * from './ui/Comment'; +export * from './model/comments'; + +// ✅ 좋은 예시 +export { Comment } from './ui/comment'; +export { CommentForm } from './ui/comment-form'; +export { useComments } from './model/use-comments'; +export type { Comment, CommentFormData } from './model/types'; +``` + +### 3. 슬라이스 독립성 + +- **Zero Coupling**: 같은 레이어의 슬라이스들은 서로 독립적이어야 합니다 +- **High Cohesion**: 슬라이스는 주요 목표와 관련된 대부분의 코드를 포함해야 합니다 +- 예외: Entities 레이어에서 필요한 경우 `@x` 표기법으로 cross-import 가능 + +### 4. 세그먼트 명명 규칙 + +세그먼트 이름은 **목적**을 설명해야 하며, **본질**을 설명하면 안 됩니다: + +- ❌ **나쁜 이름**: `components`, `hooks`, `types` (무엇인지만 설명) +- ✅ **좋은 이름**: `ui`, `api`, `model`, `lib` (왜 있는지 설명) + +## 코드 규칙 + +- TypeScript 사용 (strict mode) +- 함수형 컴포넌트 사용 +- Named exports 선호 +- Tailwind CSS 사용 +- ESLint + Prettier 적용 +- Public API는 명시적으로 정의 (와일드카드 export 지양) + +## 아키텍처 린팅 + +코드베이스의 FSD 규칙 준수를 자동으로 검증하려면 [Steiger](https://github.com/feature-sliced/steiger)를 사용하세요. + +```bash +# Steiger 설치 및 실행 +pnpm add -D @feature-sliced/steiger +npx steiger src +``` diff --git a/apps/(template)/app/(with-base-layout)/layout.tsx b/apps/(template)/app/(with-base-layout)/layout.tsx new file mode 100644 index 000000000..1d182a0ee --- /dev/null +++ b/apps/(template)/app/(with-base-layout)/layout.tsx @@ -0,0 +1,31 @@ +"use client"; + +import { useCallback } from "react"; +import { usePathname, useRouter } from "next/navigation"; +import { useQueryClient } from "@tanstack/react-query"; +import { BaseLayout } from "@common/components"; +import type { MenuItem } from "@/entities/menu"; +import { PageLayout } from "@/shared/ui/page-layout"; + +export default function WithBaseLayout({ children }: { children: React.ReactNode }) { + const router = useRouter(); + const pathname = usePathname(); + const queryClient = useQueryClient(); + + const handleMenuClick = useCallback( + (item: MenuItem) => { + router.push(item.urlPath); + }, + [router], + ); + + return ( + + {children} + + ); +} diff --git a/apps/(template)/app/(with-base-layout)/page.tsx b/apps/(template)/app/(with-base-layout)/page.tsx new file mode 100644 index 000000000..e118e732e --- /dev/null +++ b/apps/(template)/app/(with-base-layout)/page.tsx @@ -0,0 +1,9 @@ +"use client"; + +export default function MainPage() { + return ( +
+

Home

+
+ ); +} diff --git a/apps/(template)/app/README.md b/apps/(template)/app/README.md new file mode 100644 index 000000000..5ab63ea34 --- /dev/null +++ b/apps/(template)/app/README.md @@ -0,0 +1,169 @@ +# App Layer + +**App**은 애플리케이션 전체에 영향을 미치는 기술적, 비즈니스적 사항을 다루는 최상위 레이어입니다. + +## 목적 + +- 애플리케이션의 진입점(entrypoint) 제공 +- 전역 Context Providers 설정 +- 라우터 구성 +- 글로벌 스타일 정의 +- 전역 상태 저장소 설정 +- 애널리틱스 및 모니터링 설정 + +## 특징 + +- **슬라이스 없음**: App 레이어는 Shared처럼 슬라이스가 없고 세그먼트로만 구성됩니다 +- **전체 앱 관심사**: 특정 비즈니스 도메인이 아닌 앱 전체와 관련된 코드만 포함 +- **최상위 레이어**: 모든 하위 레이어의 모듈을 자유롭게 import 가능 + +## 구조 (Next.js App Router) + +Next.js를 사용하는 경우, App 레이어는 주로 `app/` 디렉토리로 표현됩니다: + +``` +app/ +├── (routes)/ # 라우트 그룹 및 페이지 +│ ├── (auth)/ +│ │ ├── login/ +│ │ │ └── page.tsx +│ │ └── register/ +│ │ └── page.tsx +│ └── (main)/ +│ ├── dashboard/ +│ │ └── page.tsx +│ └── settings/ +│ └── page.tsx +├── layout.tsx # 루트 레이아웃 +├── page.tsx # 홈 페이지 +├── globals.css # 글로벌 스타일 +├── providers.tsx # 전역 Providers 설정 +└── error.tsx # 글로벌 에러 핸들러 +``` + +### 일반적인 FSD 세그먼트 구조 + +FSD 표준 세그먼트를 사용하는 경우: + +``` +app/ +├── entrypoint/ # 애플리케이션 진입점 (프레임워크별) +│ └── index.tsx +├── providers/ # Context Providers +│ ├── query-provider.tsx +│ ├── theme-provider.tsx +│ └── index.tsx +├── styles/ # 글로벌 스타일 +│ ├── globals.css +│ └── theme.css +├── store/ # 글로벌 상태 저장소 설정 +│ └── index.ts +└── routes/ # 라우터 설정 + └── index.tsx +``` + +## 예시 + +### Next.js Providers 설정 + +```typescript +// app/providers.tsx +'use client'; + +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { ThemeProvider } from 'next-themes'; +import { useState } from 'react'; + +export function Providers({ children }: { children: React.ReactNode }) { + const [queryClient] = useState(() => new QueryClient({ + defaultOptions: { + queries: { + staleTime: 60 * 1000, + }, + }, + })); + + return ( + + + {children} + + + ); +} + +// app/layout.tsx +import { Providers } from './providers'; +import './globals.css'; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + + {children} + + + + ); +} +``` + +### 글로벌 설정 + +```typescript +// app/config/analytics.ts +export function initAnalytics() { + if (typeof window !== 'undefined') { + // 애널리틱스 초기화 + } +} + +// app/config/monitoring.ts +export function initMonitoring() { + // 에러 모니터링 설정 +} +``` + +## Next.js App Router와 FSD + +Next.js의 `app/` 디렉토리는 FSD의 **App 레이어**와 **Pages 레이어**를 동시에 담당합니다: + +- **루트 레벨 파일들** (`layout.tsx`, `providers.tsx`, `globals.css`): App 레이어 +- **라우트 폴더들** (`dashboard/`, `login/` 등): Pages 레이어 + +각 페이지별 비즈니스 로직은: +- 재사용되지 않는 UI는 페이지 슬라이스(`app/dashboard/` 등) 안에 유지 +- 재사용되는 기능은 `features/` 레이어로 분리 +- 여러 페이지에서 재사용되는 큰 UI 블록은 `widgets/` 레이어로 분리 + +## 레이어 의존성 규칙 + +✅ **허용되는 의존성**: +- 모든 하위 레이어 (`pages`, `widgets`, `features`, `entities`, `shared`) +- 같은 `app` 레이어 내의 다른 세그먼트 +- 외부 라이브러리 + +❌ **금지된 의존성**: +- 없음 (App은 최상위 레이어) + +## 주의사항 + +1. **비대화 방지**: App 레이어가 너무 커지면 관리가 어려워집니다. 대부분의 로직은 하위 레이어에 배치하세요 +2. **전역 관심사만**: 특정 페이지나 기능에만 관련된 코드는 해당 레이어로 이동 +3. **라우터 레벨 로직**: 라우터 및 서버 레벨 로직은 이 레이어에 유지 +4. **Processes 레이어 사용 지양**: Processes 레이어는 deprecated되었으므로 해당 로직은 `app`이나 `features`에 배치 + +## FSD v2.1 권장사항 + +최신 FSD 버전(v2.1)은 **"pages-first"** 접근 방식을 권장합니다: + +- 더 많은 코드를 페이지 슬라이스에 유지 +- 큰 UI 블록과 재사용되지 않는 비즈니스 로직은 즉시 추출하지 말고 페이지에 보관 +- 실제로 재사용이 필요할 때만 `features`나 `widgets`로 분리 + +이 접근 방식은 과도한 추상화를 방지하고 코드를 더 쉽게 탐색할 수 있게 합니다. diff --git a/apps/(template)/app/globals.css b/apps/(template)/app/globals.css new file mode 100644 index 000000000..60bc92fb6 --- /dev/null +++ b/apps/(template)/app/globals.css @@ -0,0 +1,15 @@ +@import "@common/assets/styles/base.css"; +@import "@common/assets/styles/diveicon.css"; +@import "@common/components/index.css"; + +@tailwind base; +@tailwind components; +@tailwind utilities; + +html, +body { + color: var(--foreground); + background: var(--background); + font-family: var(--font-noto-sans-kr), sans-serif; + font-size: 10px; +} diff --git a/apps/(template)/app/layout.tsx b/apps/(template)/app/layout.tsx new file mode 100644 index 000000000..d152e0da3 --- /dev/null +++ b/apps/(template)/app/layout.tsx @@ -0,0 +1,30 @@ +import type { Metadata } from "next"; +import { Noto_Sans_KR } from "next/font/google"; +import { Providers } from "./providers"; +import "./globals.css"; + +const notoSansKR = Noto_Sans_KR({ + subsets: ["latin"], + weight: ["100", "300", "400", "500", "700", "900"], + display: "swap", + variable: "--font-noto-sans-kr", +}); + +export const metadata: Metadata = { + title: "행정지원시스템", + description: "행정지원시스템", +}; + +interface RootLayoutProps { + children: React.ReactNode; +} + +export default function RootLayout({ children }: RootLayoutProps) { + return ( + + + {children} + + + ); +} diff --git a/apps/(template)/app/providers.tsx b/apps/(template)/app/providers.tsx new file mode 100644 index 000000000..bc12c443b --- /dev/null +++ b/apps/(template)/app/providers.tsx @@ -0,0 +1,18 @@ +"use client"; + +import { QueryClientProvider } from "@tanstack/react-query"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +import { queryClient } from "@/shared/lib"; + +interface ProvidersProps { + children: React.ReactNode; +} + +export function Providers({ children }: ProvidersProps) { + return ( + + {children} + + + ); +} diff --git a/apps/(template)/entities/README.md b/apps/(template)/entities/README.md new file mode 100644 index 000000000..b28c10790 --- /dev/null +++ b/apps/(template)/entities/README.md @@ -0,0 +1,114 @@ +# Entities Layer + +**Entities**는 프로젝트가 다루는 실제 비즈니스 개념을 나타내는 레이어입니다. 비즈니스에서 제품을 설명할 때 사용하는 용어들(예: User, Product, Order)이 주로 엔티티가 됩니다. + +## 목적 + +- 비즈니스 엔티티의 데이터 모델 및 타입 정의 +- 엔티티 관련 API 요청 함수 관리 +- 엔티티의 시각적 표현 제공 (재사용 가능한 UI) +- 여러 페이지에서 동일한 외관을 재사용할 수 있도록 지원 + +## 구조 + +각 엔티티 슬라이스는 다음 세그먼트로 구성됩니다: + +``` +entities/ +└── [entity-name]/ + ├── model/ # 데이터 저장소, 검증 스키마, 상태 관리 + ├── api/ # 엔티티 관련 API 요청 함수 + ├── ui/ # 엔티티 시각적 표현 (선택적) + ├── lib/ # 엔티티 관련 유틸리티 (선택적) + ├── @x/ # 다른 엔티티로부터의 cross-import용 Public API (선택적) + └── index.ts # Public API +``` + +### 세그먼트 설명 + +- **model**: 데이터 모델, 타입 정의, 상태 저장소 (Zustand 등) +- **api**: 백엔드 API 호출 함수 +- **ui**: 엔티티를 표시하는 UI 컴포넌트 (완전한 UI 블록이 아니라 재사용 가능한 표현) +- **lib**: 엔티티 관련 유틸리티 함수 +- **@x**: 다른 엔티티가 이 엔티티를 import할 때 사용하는 명시적 cross-import API +- **index.ts**: 슬라이스의 Public API (외부에서 사용할 수 있는 것만 export) + +## 예시 + +```typescript +// entities/user/model/user.ts +export interface User { + id: string; + name: string; + email: string; + role: 'admin' | 'user'; +} + +// entities/user/api/get-user.ts +import type { User } from '../model/user'; + +export async function getUser(id: string): Promise { + const response = await fetch(`/api/users/${id}`); + return response.json(); +} + +// entities/user/ui/user-card.tsx +import type { User } from '../model/user'; + +interface UserCardProps { + user: User; + onClick?: () => void; +} + +export function UserCard({ user, onClick }: UserCardProps) { + return ( +
+

{user.name}

+

{user.email}

+
+ ); +} + +// entities/user/index.ts +export { type User } from './model/user'; +export { getUser } from './api/get-user'; +export { UserCard } from './ui/user-card'; +``` + +## 엔티티 간 관계 + +엔티티가 서로 연결되어 있을 때는 `@x` 표기법을 사용하여 명시적으로 cross-import를 관리하는 것이 권장됩니다: + +```typescript +// entities/artist/model/artist.ts +import type { Song } from 'entities/song/@x/artist'; + +export interface Artist { + name: string; + songs: Array; +} + +// entities/song/@x/artist.ts +export type { Song } from '../model/song'; +``` + +> `@x` 폴더를 사용하면 엔티티 간 연결을 명확하게 만들어 함께 리팩터링이 필요한 코드를 쉽게 찾을 수 있습니다. + +## 레이어 의존성 규칙 + +✅ **허용되는 의존성**: +- `shared` 레이어의 모듈 +- 같은 엔티티 슬라이스 내의 다른 세그먼트 +- 다른 엔티티 슬라이스 (필요한 경우, `@x` 표기법 권장) + +❌ **금지된 의존성**: +- `features` 레이어 +- `widgets` 레이어 +- `pages` 레이어 +- `app` 레이어 + +## 주의사항 + +1. **UI 컴포넌트는 완전한 비즈니스 로직을 포함하지 않음**: UI는 주로 props나 slots를 통해 다양한 비즈니스 로직을 연결할 수 있도록 설계 +2. **엔티티 간 비즈니스 로직은 상위 레이어에**: 엔티티 간 상호작용의 비즈니스 로직은 Features나 Pages 레이어에서 처리 +3. **데이터 관리에 집중**: 복잡한 비즈니스 로직보다는 데이터 모델, 검증, 저장에 집중 diff --git a/apps/(template)/entities/menu/index.ts b/apps/(template)/entities/menu/index.ts new file mode 100644 index 000000000..cb55d9a1c --- /dev/null +++ b/apps/(template)/entities/menu/index.ts @@ -0,0 +1 @@ +export { type MenuItem } from "./model/types"; diff --git a/apps/(template)/entities/menu/model/types.ts b/apps/(template)/entities/menu/model/types.ts new file mode 100644 index 000000000..9692821e3 --- /dev/null +++ b/apps/(template)/entities/menu/model/types.ts @@ -0,0 +1,19 @@ +export interface MenuItem { + menuCd: number; + menuKornNm: string; + menuEngNm: string; + parentId: number | null; + sortSeq: number; + iconNm?: string; + children: MenuItem[] | null; + levelNo: number; + urlPath: string; + menuLnkgTypeCd: string; + menuLnkgSn: number; + menuPath: string; + qrcd: string; + useYn: string; + otptYn: string; + newWndwYn: string; + requiredUrlPath: boolean; +} diff --git a/apps/(template)/eslint.config.mjs b/apps/(template)/eslint.config.mjs new file mode 100644 index 000000000..05e726d1b --- /dev/null +++ b/apps/(template)/eslint.config.mjs @@ -0,0 +1,18 @@ +import { defineConfig, globalIgnores } from "eslint/config"; +import nextVitals from "eslint-config-next/core-web-vitals"; +import nextTs from "eslint-config-next/typescript"; + +const eslintConfig = defineConfig([ + ...nextVitals, + ...nextTs, + // Override default ignores of eslint-config-next. + globalIgnores([ + // Default ignores of eslint-config-next: + ".next/**", + "out/**", + "build/**", + "next-env.d.ts", + ]), +]); + +export default eslintConfig; diff --git a/apps/(template)/features/README.md b/apps/(template)/features/README.md new file mode 100644 index 000000000..2be9a3e17 --- /dev/null +++ b/apps/(template)/features/README.md @@ -0,0 +1,130 @@ +# Features Layer + +**Features**는 앱의 주요 사용자 상호작용과 기능을 구현하는 레이어입니다. 사용자가 실제로 수행하는 작업들로, 주로 비즈니스 엔티티와 관련이 있습니다. + +## 목적 + +- 사용자가 중요하게 생각하는 주요 상호작용 구현 +- 재사용 가능한 비즈니스 기능 제공 +- 여러 페이지에서 사용되는 기능의 중복 제거 + +## Feature로 만들어야 하는 경우 + +> ⚠️ **중요**: 모든 것을 feature로 만들 필요는 없습니다. + +Feature로 만드는 것이 좋은 지표: +- **여러 페이지에서 재사용됨**: 예를 들어, 여러 에디터가 있고 모두 댓글 기능이 있다면, 댓글은 재사용 가능한 feature입니다 +- **중요한 비즈니스 기능**: 신규 프로젝트 참여자가 Features 폴더를 보고 앱의 주요 기능을 빠르게 파악할 수 있어야 합니다 + +Feature가 너무 많으면 중요한 기능이 묻히므로, 신규 참여자의 경험을 최적화하여 결정하세요. + +## 구조 + +각 feature 슬라이스는 다음 세그먼트로 구성됩니다: + +``` +features/ +└── [feature-name]/ + ├── ui/ # 상호작용을 수행하는 UI (폼, 버튼 등) + ├── api/ # 액션 수행에 필요한 API 호출 (선택적) + ├── model/ # 검증 및 내부 상태 관리 (선택적) + ├── lib/ # 유틸리티 함수 (선택적) + ├── config/ # feature flags (선택적) + └── index.ts # Public API +``` + +### 세그먼트 설명 + +- **ui**: 사용자 상호작용을 위한 UI 컴포넌트 (폼, 모달, 버튼 등) +- **api**: feature 실행에 필요한 API 요청 함수 +- **model**: 폼 검증, 내부 상태, 비즈니스 로직 +- **lib**: feature 내부에서만 사용되는 유틸리티 +- **config**: feature flags, feature별 설정값 +- **index.ts**: 슬라이스의 Public API (외부에서 사용할 수 있는 것만 export) + +## 예시 + +```typescript +// features/auth-login/ui/login-form.tsx +import { useState } from 'react'; +import { loginUser } from '../api/login-user'; +import type { LoginCredentials } from '../model/types'; + +export function LoginForm() { + const [credentials, setCredentials] = useState({ + email: '', + password: '' + }); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + await loginUser(credentials); + }; + + return ( +
+ setCredentials({ ...credentials, email: e.target.value })} + /> + setCredentials({ ...credentials, password: e.target.value })} + /> + +
+ ); +} + +// features/auth-login/api/login-user.ts +import type { LoginCredentials } from '../model/types'; + +export async function loginUser(credentials: LoginCredentials) { + const response = await fetch('/api/auth/login', { + method: 'POST', + body: JSON.stringify(credentials), + }); + return response.json(); +} + +// features/auth-login/model/types.ts +export interface LoginCredentials { + email: string; + password: string; +} + +// features/auth-login/index.ts +export { LoginForm } from './ui/login-form'; +export { loginUser } from './api/login-user'; +export type { LoginCredentials } from './model/types'; +``` + +## 레이어 의존성 규칙 + +✅ **허용되는 의존성**: +- `shared` 레이어의 모듈 +- `entities` 레이어의 모듈 +- 같은 feature 슬라이스 내의 다른 세그먼트 + +❌ **금지된 의존성**: +- 다른 `features` 슬라이스 (feature 간 직접 의존 금지) +- `widgets` 레이어 +- `pages` 레이어 +- `app` 레이어 + +> 💡 **팁**: 여러 features가 공통 기능을 필요로 한다면, 그 기능을 `shared` 레이어로 이동시키거나 `entities` 레이어에서 관리하세요. + +## 명명 규칙 + +- 소문자와 대시 사용: `auth-login`, `post-create`, `comment-edit` +- 동사 형태 권장: 사용자 액션을 나타내는 이름 사용 +- 명확하고 구체적으로: 기능의 목적이 명확하게 드러나도록 + +## 주의사항 + +1. **하나의 기능에만 집중**: 각 feature는 단일 사용자 시나리오를 구현해야 합니다 +2. **재사용성 고려**: 여러 페이지에서 사용될 수 있도록 설계 +3. **독립성 유지**: 다른 features에 의존하지 않고 독립적으로 작동 +4. **적절한 추상화**: 너무 많은 feature를 만들지 말고, 중요한 기능 위주로 구성 diff --git a/apps/(template)/next.config.ts b/apps/(template)/next.config.ts new file mode 100644 index 000000000..8864de0e7 --- /dev/null +++ b/apps/(template)/next.config.ts @@ -0,0 +1,8 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + reactStrictMode: true, + transpilePackages: ["@common/assets"], +}; + +export default nextConfig; diff --git a/apps/(template)/package.json b/apps/(template)/package.json new file mode 100644 index 000000000..fb27f8af7 --- /dev/null +++ b/apps/(template)/package.json @@ -0,0 +1,36 @@ +{ + "name": "as", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint", + "lint:fsd": "steiger ." + }, + "dependencies": { + "@common/assets": "workspace:*", + "@common/components": "workspace:*", + "@common/network": "workspace:*", + "@hookform/resolvers": "^4.1.3", + "@tanstack/react-query": "^5.75.5", + "@tanstack/react-query-devtools": "^5.75.5", + "@tanstack/react-table": "^8.21.2", + "axios": "^1.8.1", + "dayjs": "^1.11.13", + "next": "15.3.8", + "react-hook-form": "^7.54.2", + "zod": "^3.24.2" + }, + "devDependencies": { + "eslint": "^9.0.0", + "eslint-config-next": "15.4.0", + "postcss": "^8.4.49", + "prettier": "^3.4.2", + "prettier-plugin-tailwindcss": "^0.6.11", + "steiger": "^0.5.11", + "tailwindcss": "^3.4.14", + "typescript": "^5.7.2" + } +} \ No newline at end of file diff --git a/apps/(template)/postcss.config.mjs b/apps/(template)/postcss.config.mjs new file mode 100644 index 000000000..1a69fd2a4 --- /dev/null +++ b/apps/(template)/postcss.config.mjs @@ -0,0 +1,8 @@ +/** @type {import('postcss-load-config').Config} */ +const config = { + plugins: { + tailwindcss: {}, + }, +}; + +export default config; diff --git a/apps/(template)/shared/README.md b/apps/(template)/shared/README.md new file mode 100644 index 000000000..2b247021d --- /dev/null +++ b/apps/(template)/shared/README.md @@ -0,0 +1,149 @@ +# Shared Layer + +**Shared**는 애플리케이션의 기반을 형성하는 레이어입니다. 외부 세계(백엔드, 서드파티 라이브러리, 환경)와의 연결을 생성하고, 높은 응집력을 가진 자체 라이브러리를 정의하는 곳입니다. + +## 목적 + +- 외부 시스템 및 라이브러리와의 통합 +- 프로젝트 전체에서 사용되는 공통 코드 제공 +- 비즈니스 로직이 없는 재사용 가능한 유틸리티 및 UI 제공 +- 글로벌 설정 및 환경 변수 관리 + +## 특징 + +- **슬라이스 없음**: Shared 레이어는 비즈니스 도메인이 없으므로 슬라이스로 나뉘지 않습니다 +- **세그먼트 구성**: 기술적 목적에 따라 세그먼트로 직접 구성됩니다 +- **자유로운 import**: Shared 내의 모든 파일은 서로를 참조하고 import할 수 있습니다 + +## 구조 + +``` +shared/ +├── ui/ # 애플리케이션 UI Kit +│ ├── button/ +│ ├── input/ +│ ├── modal/ +│ └── index.ts +├── api/ # API 클라이언트 및 백엔드 엔드포인트 함수 +│ ├── client.ts +│ └── index.ts +├── lib/ # 내부 라이브러리 모음 +│ ├── dates/ # 날짜 관련 유틸리티 +│ ├── colors/ # 색상 관련 유틸리티 +│ ├── text/ # 텍스트 처리 유틸리티 +│ └── index.ts +├── config/ # 환경 변수, 글로벌 feature flags +│ ├── env.ts +│ └── index.ts +├── types/ # 공통 타입 정의 +│ └── index.ts +├── i18n/ # 번역 설정, 글로벌 번역 문자열 +│ └── index.ts +└── routes/ # 라우트 상수 또는 패턴 + └── index.ts +``` + +## 세그먼트 설명 + +### 📁 ui +애플리케이션의 UI Kit로, 비즈니스 로직을 포함하지 않는 재사용 가능한 UI 컴포넌트들입니다. + +- Button, Input, Modal 같은 기본 컴포넌트 +- 비즈니스 로직은 없지만 UI 로직은 포함 가능 (예: Autocomplete, SearchBar) +- 비즈니스 테마 컴포넌트도 허용 (예: 회사 로고, 페이지 레이아웃) + +### 📁 api +API 클라이언트 및 특정 백엔드 엔드포인트 요청 함수입니다. + +- HTTP 클라이언트 설정 (Axios, Fetch 등) +- 인증 인터셉터 +- 공통 에러 핸들링 + +### 📁 lib +내부 라이브러리 모음으로, 각 라이브러리는 **하나의 관심사**에 집중해야 합니다. + +> ⚠️ **중요**: `helpers`나 `utils` 폴더처럼 덤프 폴더로 만들지 마세요. 각 라이브러리는 명확한 초점 영역(예: dates, colors, text)을 가져야 하며, README로 문서화해야 합니다. + +### 📁 config +환경 변수, 글로벌 feature flags 및 앱 전체 설정입니다. + +### 📁 types +프로젝트 전체에서 사용되는 공통 타입 정의입니다. + +### 📁 i18n +번역 설정 코드 및 글로벌 번역 문자열입니다. + +### 📁 routes +라우트 상수 또는 라우트 매칭 패턴입니다. + +## 예시 + +```typescript +// shared/ui/button/button.tsx +interface ButtonProps { + children: React.ReactNode; + variant?: 'primary' | 'secondary'; + onClick?: () => void; +} + +export function Button({ children, variant = 'primary', onClick }: ButtonProps) { + return ( + + ); +} + +// shared/api/client.ts +import axios from 'axios'; + +export const apiClient = axios.create({ + baseURL: process.env.NEXT_PUBLIC_API_URL, + timeout: 10000, +}); + +// shared/lib/dates/format-date.ts +import dayjs from 'dayjs'; + +export function formatDate(date: Date, format: string = 'YYYY-MM-DD'): string { + return dayjs(date).format(format); +} + +// shared/config/env.ts +export const ENV = { + apiUrl: process.env.NEXT_PUBLIC_API_URL!, + isDev: process.env.NODE_ENV === 'development', +} as const; +``` + +## 레이어 의존성 규칙 + +✅ **허용되는 의존성**: +- 같은 `shared` 레이어 내의 다른 세그먼트 (자유로운 cross-import) +- 외부 라이브러리 및 패키지 + +❌ **금지된 의존성**: +- `entities` 레이어 +- `features` 레이어 +- `widgets` 레이어 +- `pages` 레이어 +- `app` 레이어 + +> 💡 **원칙**: Shared는 가장 낮은 레이어이므로 상위 레이어에 의존하지 않습니다. + +## 세그먼트 명명 규칙 + +세그먼트 이름은 **내용의 목적**을 설명해야 하며, **본질**을 설명하면 안 됩니다. + +❌ **나쁜 예시**: `components`, `hooks`, `types` (본질을 설명) +✅ **좋은 예시**: `ui`, `api`, `lib/dates`, `config` (목적을 설명) + +## 주의사항 + +1. **비즈니스 로직 없음**: Shared는 순수한 기술적 코드만 포함 +2. **높은 재사용성**: 앱 전체에서 사용될 수 있어야 함 +3. **명확한 라이브러리 구조**: `lib` 내부는 관심사별로 명확하게 분리 +4. **문서화**: 각 라이브러리는 README로 사용법과 목적을 문서화 diff --git a/apps/(template)/shared/api/client.ts b/apps/(template)/shared/api/client.ts new file mode 100644 index 000000000..d7c4a8f1d --- /dev/null +++ b/apps/(template)/shared/api/client.ts @@ -0,0 +1,9 @@ +import { Client } from "@common/network"; +import { PROXY_HOST, SITE_ID } from "@/shared/config/env"; + +const BASE_URL = `${PROXY_HOST}/server`; + +export const apiClient = Client.getInstance({ + baseURL: BASE_URL, + siteId: SITE_ID, +}); diff --git a/apps/(template)/shared/api/index.ts b/apps/(template)/shared/api/index.ts new file mode 100644 index 000000000..c5eb98066 --- /dev/null +++ b/apps/(template)/shared/api/index.ts @@ -0,0 +1 @@ +export { apiClient } from "./client"; diff --git a/apps/(template)/shared/config/env.ts b/apps/(template)/shared/config/env.ts new file mode 100644 index 000000000..4f08c722a --- /dev/null +++ b/apps/(template)/shared/config/env.ts @@ -0,0 +1,11 @@ +export const PORT = process.env.NEXT_PUBLIC_PORT || "3000"; +export const PROXY_HOST = + process.env.NEXT_PUBLIC_PROXY_HOST || `http://localhost:${PORT}`; +export const SERVER_API_URL = process.env.NEXT_PUBLIC_SERVER_API_URL; +export const SITE_ID = process.env.NEXT_PUBLIC_SITE_ID || "AS"; +export const CLAIM_NAME = process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization"; +export const ACCESS_TOKEN = + process.env.NEXT_PUBLIC_ACCESS_TOKEN || "access-token"; +export const REFRESH_TOKEN = + process.env.NEXT_PUBLIC_REFRESH_TOKEN || "refresh-token"; +export const AUTH_USER_ID = process.env.NEXT_PUBLIC_AUTH_USER_ID || "token-id"; diff --git a/apps/(template)/shared/config/index.ts b/apps/(template)/shared/config/index.ts new file mode 100644 index 000000000..c76f8cd47 --- /dev/null +++ b/apps/(template)/shared/config/index.ts @@ -0,0 +1 @@ +export * from "./env"; diff --git a/apps/(template)/shared/lib/dayjs.ts b/apps/(template)/shared/lib/dayjs.ts new file mode 100644 index 000000000..28b3f908b --- /dev/null +++ b/apps/(template)/shared/lib/dayjs.ts @@ -0,0 +1,12 @@ +import dayjs from "dayjs"; +import "dayjs/locale/ko"; +import utc from "dayjs/plugin/utc"; +import timezone from "dayjs/plugin/timezone"; +import relativeTime from "dayjs/plugin/relativeTime"; + +dayjs.extend(utc); +dayjs.extend(timezone); +dayjs.extend(relativeTime); +dayjs.locale("ko"); + +export { dayjs }; diff --git a/apps/(template)/shared/lib/index.ts b/apps/(template)/shared/lib/index.ts new file mode 100644 index 000000000..51e9c2c5d --- /dev/null +++ b/apps/(template)/shared/lib/index.ts @@ -0,0 +1,2 @@ +export { queryClient } from "./query-client"; +export { dayjs } from "./dayjs"; diff --git a/apps/(template)/shared/lib/query-client.ts b/apps/(template)/shared/lib/query-client.ts new file mode 100644 index 000000000..0da2fb899 --- /dev/null +++ b/apps/(template)/shared/lib/query-client.ts @@ -0,0 +1,11 @@ +import { QueryClient } from "@tanstack/react-query"; + +export const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: 1, + refetchOnWindowFocus: false, + staleTime: 60 * 1000, + }, + }, +}); diff --git a/apps/(template)/shared/model/index.ts b/apps/(template)/shared/model/index.ts new file mode 100644 index 000000000..15ddf4254 --- /dev/null +++ b/apps/(template)/shared/model/index.ts @@ -0,0 +1,29 @@ +export interface BaseEntity { + id: string; + createdAt: string; + updatedAt: string; +} + +export interface PaginationParams { + page: number; + limit: number; +} + +export interface PaginationResponse { + data: T[]; + total: number; + page: number; + limit: number; +} + +export interface ApiResponse { + success: boolean; + data: T; + message?: string; +} + +export interface ApiError { + message: string; + code?: string; + status?: number; +} diff --git a/apps/(template)/shared/ui/index.ts b/apps/(template)/shared/ui/index.ts new file mode 100644 index 000000000..460542995 --- /dev/null +++ b/apps/(template)/shared/ui/index.ts @@ -0,0 +1 @@ +export { PageLayout } from "./page-layout"; diff --git a/apps/(template)/shared/ui/page-layout.tsx b/apps/(template)/shared/ui/page-layout.tsx new file mode 100644 index 000000000..ecefc36bd --- /dev/null +++ b/apps/(template)/shared/ui/page-layout.tsx @@ -0,0 +1,35 @@ +"use client"; + +import { PageTitle } from "@common/components"; + +const mockCurrentMenuInfo = { + menuCd: 0, + menuKornNm: "메인", + menuEngNm: "Main", + parentId: null, + sortSeq: 0, + children: null, + levelNo: 0, + urlPath: "/", + menuLnkgTypeCd: "", + menuLnkgSn: 0, + menuPath: "메인", + qrcd: "", + useYn: "Y", + otptYn: "Y", + newWndwYn: "N", + requiredUrlPath: false, +}; + +function PageLayout({ children }: { children: React.ReactNode }) { + const menuPaths = mockCurrentMenuInfo.menuPath.split(">").map((path) => path.trim()); + + return ( +
+ + {children} +
+ ); +} + +export { PageLayout }; diff --git a/apps/(template)/tailwind.config.ts b/apps/(template)/tailwind.config.ts new file mode 100644 index 000000000..c02405aca --- /dev/null +++ b/apps/(template)/tailwind.config.ts @@ -0,0 +1,29 @@ +import type { Config } from "tailwindcss"; +import presetConfig from "../../tailwind.config.preset"; + +const config: Config = { + presets: [presetConfig], + content: [ + "./pages/**/*.{js,ts,jsx,tsx,mdx}", + "./shared/**/*.{js,ts,jsx,tsx,mdx}", + "./entities/**/*.{js,ts,jsx,tsx,mdx}", + "./features/**/*.{js,ts,jsx,tsx,mdx}", + "./widgets/**/*.{js,ts,jsx,tsx,mdx}", + "./app/**/*.{js,ts,jsx,tsx,mdx}", + "../../packages/common/components/dist/**/*.{js,ts,jsx,tsx,mdx}", + ], + theme: { + extend: { + colors: { + background: "var(--background)", + foreground: "var(--foreground)", + }, + fontFamily: { + sans: ["var(--font-noto-sans-kr)", "Noto Sans KR", "sans-serif"], + }, + }, + }, + plugins: [], +}; + +export default config; diff --git a/apps/(template)/tsconfig.json b/apps/(template)/tsconfig.json new file mode 100644 index 000000000..15b023415 --- /dev/null +++ b/apps/(template)/tsconfig.json @@ -0,0 +1,35 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./*"], + "@/app/*": ["./app/*"], + "@/shared/*": ["./shared/*"], + "@/entities/*": ["./entities/*"], + "@/features/*": ["./features/*"], + "@/widgets/*": ["./widgets/*"], + "@/pages/*": ["./pages/*"], + "@common/components": ["../../packages/common/components/dist/"], + "@common/network": ["../../packages/common/network/"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/apps/(template)/widgets/README.md b/apps/(template)/widgets/README.md new file mode 100644 index 000000000..cd160abd9 --- /dev/null +++ b/apps/(template)/widgets/README.md @@ -0,0 +1,135 @@ +# Widgets Layer + +**Widgets**는 큰 규모의 독립적인 UI 블록을 위한 레이어입니다. 여러 features와 entities를 조합하여 완전한 기능을 제공하는 복합 컴포넌트를 담습니다. + +## 목적 + +- 큰 규모의 독립적인 UI 블록 구성 +- 여러 features와 entities를 하나의 응집력 있는 UI로 결합 +- 여러 페이지에서 재사용되는 복잡한 UI 제공 +- 페이지 레벨 레이아웃 및 섹션 관리 + +## Widget으로 만들어야 하는 경우 + +Widget은 다음 조건에 해당할 때 사용하세요: + +✅ **Widget으로 만들기 좋은 경우**: +- 여러 페이지에서 재사용되는 큰 UI 블록 +- 페이지에 여러 개의 큰 독립적 블록이 있고, 이것이 그 중 하나인 경우 +- 페이지 레벨 레이아웃 컴포넌트 (nested routing 시스템 사용 시) + +❌ **Widget으로 만들지 말아야 하는 경우**: +- UI 블록이 페이지의 대부분을 차지하고 재사용되지 않는 경우 → 페이지 슬라이스 내부에 직접 배치 +- 작은 재사용 컴포넌트 → `shared/ui`에 배치 + +## 구조 + +각 widget 슬라이스는 다음 세그먼트로 구성됩니다: + +``` +widgets/ +└── [widget-name]/ + ├── ui/ # 위젯의 메인 UI 컴포넌트 + ├── model/ # 위젯 내부 상태 관리 (선택적) + ├── api/ # 위젯 전용 API 호출 (선택적) + ├── lib/ # 위젯 내부 유틸리티 (선택적) + └── index.ts # Public API +``` + +### 세그먼트 설명 + +- **ui**: 여러 features와 entities를 조합한 위젯 UI +- **model**: 위젯 내부 상태 및 로직 (선택적) +- **api**: 위젯이 필요로 하는 데이터 fetching (선택적) +- **lib**: 위젯 내부에서만 사용되는 헬퍼 함수 (선택적) +- **index.ts**: 슬라이스의 Public API (외부에서 사용할 수 있는 것만 export) + +## 예시 + +```typescript +// widgets/header/ui/header.tsx +import { Logo } from 'shared/ui/logo'; +import { Navigation } from 'shared/ui/navigation'; +import { UserMenu } from 'features/user-menu'; +import { NotificationBell } from 'features/notifications'; + +export function Header() { + return ( +
+ + + + +
+ ); +} + +// widgets/product-list/ui/product-list.tsx +import { useProducts } from '../api/use-products'; +import { ProductCard } from 'entities/product'; +import { AddToCartButton } from 'features/cart-add-item'; + +export function ProductList() { + const { data: products, isLoading } = useProducts(); + + if (isLoading) return
로딩 중...
; + + return ( +
+ {products.map((product) => ( +
+ + +
+ ))} +
+ ); +} + +// widgets/product-list/api/use-products.ts +import { useQuery } from '@tanstack/react-query'; +import { getProducts } from 'entities/product'; + +export function useProducts() { + return useQuery({ + queryKey: ['products'], + queryFn: getProducts, + }); +} + +// widgets/product-list/index.ts +export { ProductList } from './ui/product-list'; +``` + +## 레이어 의존성 규칙 + +✅ **허용되는 의존성**: +- `shared` 레이어의 모듈 +- `entities` 레이어의 모듈 +- `features` 레이어의 모듈 +- 같은 widget 슬라이스 내의 다른 세그먼트 + +❌ **금지된 의존성**: +- 다른 `widgets` 슬라이스 (widget 간 직접 의존 금지) +- `pages` 레이어 +- `app` 레이어 + +## 명명 규칙 + +- 소문자와 대시 사용: `header`, `sidebar`, `product-list` +- 명사 형태 권장: UI 블록의 성격을 나타내는 이름 사용 +- 구체적으로: 위젯의 역할이 명확하게 드러나도록 + +## Nested Routing 사용 시 + +Remix 같은 nested routing 시스템을 사용한다면: +- Widgets 레이어를 flat routing의 Pages 레이어처럼 사용 가능 +- 완전한 라우터 블록 생성 (데이터 fetching, loading states, error boundaries 포함) +- 페이지 레이아웃을 이 레이어에 저장 가능 + +## 주의사항 + +1. **독립성 유지**: 각 위젯은 독립적으로 작동 가능해야 합니다 +2. **적절한 크기**: 너무 작은 컴포넌트는 `shared/ui`에, 재사용되지 않으면 `pages`에 배치 +3. **응집력**: 관련된 features와 entities를 의미 있게 조합 +4. **재사용 최적화**: 여러 페이지에서 동일한 방식으로 사용될 수 있도록 설계 diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cc4dd0f6f..1a7066496 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -51,6 +51,70 @@ importers: specifier: 2.4.2 version: 2.4.2 + apps/(template): + dependencies: + '@common/assets': + specifier: workspace:* + version: link:../../packages/common/assets + '@common/components': + specifier: workspace:* + version: link:../../packages/common/components + '@common/network': + specifier: workspace:* + version: link:../../packages/common/network + '@hookform/resolvers': + specifier: ^4.1.3 + version: 4.1.3(react-hook-form@7.68.0(react@19.2.3)) + '@tanstack/react-query': + specifier: ^5.75.5 + version: 5.90.12(react@19.2.3) + '@tanstack/react-query-devtools': + specifier: ^5.75.5 + version: 5.91.1(@tanstack/react-query@5.90.12(react@19.2.3))(react@19.2.3) + '@tanstack/react-table': + specifier: ^8.21.2 + version: 8.21.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + axios: + specifier: ^1.8.1 + version: 1.8.1 + dayjs: + specifier: ^1.11.13 + version: 1.11.19 + next: + specifier: 15.3.8 + version: 15.3.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + react-hook-form: + specifier: ^7.54.2 + version: 7.68.0(react@19.2.3) + zod: + specifier: ^3.24.2 + version: 3.25.76 + devDependencies: + eslint: + specifier: ^9.0.0 + version: 9.39.2(jiti@1.21.7) + eslint-config-next: + specifier: 15.4.0 + version: 15.4.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + postcss: + specifier: ^8.4.49 + version: 8.5.6 + prettier: + specifier: ^3.4.2 + version: 3.7.4 + prettier-plugin-tailwindcss: + specifier: ^0.6.11 + version: 0.6.14(prettier@3.7.4) + steiger: + specifier: ^0.5.11 + version: 0.5.11(typescript@5.9.3) + tailwindcss: + specifier: ^3.4.14 + version: 3.4.19(yaml@2.8.2) + typescript: + specifier: ^5.7.2 + version: 5.9.3 + apps/cm: dependencies: '@common/assets': @@ -705,7 +769,7 @@ importers: version: 3.4.0 tailwindcss-animate: specifier: ^1.0.7 - version: 1.0.7(tailwindcss@3.4.19) + version: 1.0.7(tailwindcss@3.4.19(yaml@2.8.2)) zod: specifier: ^3.24.2 version: 3.25.76 @@ -727,7 +791,7 @@ importers: version: 8.5.6 tailwindcss: specifier: ^3.4.1 - version: 3.4.19 + version: 3.4.19(yaml@2.8.2) typescript: specifier: ^5 version: 5.9.3 @@ -1612,7 +1676,7 @@ importers: version: 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.7.4)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@8.6.14(prettier@3.7.4))(typescript@5.6.3) '@storybook/react-vite': specifier: ^8.5.3 - version: 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.7.4)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.53.3)(storybook@8.6.14(prettier@3.7.4))(typescript@5.6.3)(vite@6.4.1(@types/node@22.19.2)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2)) + version: 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.7.4)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.53.3)(storybook@8.6.14(prettier@3.7.4))(typescript@5.6.3)(vite@6.4.1(@types/node@22.19.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2)) '@storybook/test': specifier: ^8.5.3 version: 8.6.14(storybook@8.6.14(prettier@3.7.4)) @@ -1624,22 +1688,22 @@ importers: version: 22.19.2 '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.7.0(vite@6.4.1(@types/node@22.19.2)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2)) + version: 4.7.0(vite@6.4.1(@types/node@22.19.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2)) autoprefixer: specifier: ^10.4.20 version: 10.4.22(postcss@8.5.6) eslint: specifier: ^9.17.0 - version: 9.39.2(jiti@1.21.7) + version: 9.39.2(jiti@2.6.1) eslint-plugin-react-hooks: specifier: ^5.0.0 - version: 5.2.0(eslint@9.39.2(jiti@1.21.7)) + version: 5.2.0(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-react-refresh: specifier: ^0.4.16 - version: 0.4.24(eslint@9.39.2(jiti@1.21.7)) + version: 0.4.24(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-storybook: specifier: ^0.11.2 - version: 0.11.6(eslint@9.39.2(jiti@1.21.7))(typescript@5.6.3) + version: 0.11.6(eslint@9.39.2(jiti@2.6.1))(typescript@5.6.3) globals: specifier: ^15.14.0 version: 15.15.0 @@ -1660,19 +1724,19 @@ importers: version: 3.4.19(yaml@2.8.2) tsup: specifier: 8.3.6 - version: 8.3.6(jiti@1.21.7)(postcss@8.5.6)(typescript@5.6.3)(yaml@2.8.2) + version: 8.3.6(jiti@2.6.1)(postcss@8.5.6)(typescript@5.6.3)(yaml@2.8.2) typescript: specifier: ~5.6.2 version: 5.6.3 typescript-eslint: specifier: ^8.18.2 - version: 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.6.3) + version: 8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.6.3) vite: specifier: ^6.0.5 - version: 6.4.1(@types/node@22.19.2)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2) + version: 6.4.1(@types/node@22.19.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2) vite-tsconfig-paths: specifier: 5.1.4 - version: 5.1.4(typescript@5.6.3)(vite@6.4.1(@types/node@22.19.2)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2)) + version: 5.1.4(typescript@5.6.3)(vite@6.4.1(@types/node@22.19.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2)) packages/common/config: devDependencies: @@ -7956,15 +8020,6 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 - '@joshwooding/vite-plugin-react-docgen-typescript@0.5.0(typescript@5.6.3)(vite@6.4.1(@types/node@22.19.2)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2))': - dependencies: - glob: 10.5.0 - magic-string: 0.27.0 - react-docgen-typescript: 2.4.0(typescript@5.6.3) - vite: 6.4.1(@types/node@22.19.2)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2) - optionalDependencies: - typescript: 5.6.3 - '@joshwooding/vite-plugin-react-docgen-typescript@0.5.0(typescript@5.6.3)(vite@6.4.1(@types/node@22.19.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2))': dependencies: glob: 10.5.0 @@ -8858,14 +8913,6 @@ snapshots: react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - '@storybook/builder-vite@8.6.14(storybook@8.6.14(prettier@3.7.4))(vite@6.4.1(@types/node@22.19.2)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2))': - dependencies: - '@storybook/csf-plugin': 8.6.14(storybook@8.6.14(prettier@3.7.4)) - browser-assert: 1.2.1 - storybook: 8.6.14(prettier@3.7.4) - ts-dedent: 2.2.0 - vite: 6.4.1(@types/node@22.19.2)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2) - '@storybook/builder-vite@8.6.14(storybook@8.6.14(prettier@3.7.4))(vite@6.4.1(@types/node@22.19.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2))': dependencies: '@storybook/csf-plugin': 8.6.14(storybook@8.6.14(prettier@3.7.4)) @@ -8935,28 +8982,6 @@ snapshots: react-dom: 19.2.3(react@19.2.3) storybook: 8.6.14(prettier@3.7.4) - '@storybook/react-vite@8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.7.4)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.53.3)(storybook@8.6.14(prettier@3.7.4))(typescript@5.6.3)(vite@6.4.1(@types/node@22.19.2)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2))': - dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.5.0(typescript@5.6.3)(vite@6.4.1(@types/node@22.19.2)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2)) - '@rollup/pluginutils': 5.3.0(rollup@4.53.3) - '@storybook/builder-vite': 8.6.14(storybook@8.6.14(prettier@3.7.4))(vite@6.4.1(@types/node@22.19.2)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2)) - '@storybook/react': 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.7.4)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@8.6.14(prettier@3.7.4))(typescript@5.6.3) - find-up: 5.0.0 - magic-string: 0.30.21 - react: 19.2.3 - react-docgen: 7.1.1 - react-dom: 19.2.3(react@19.2.3) - resolve: 1.22.11 - storybook: 8.6.14(prettier@3.7.4) - tsconfig-paths: 4.2.0 - vite: 6.4.1(@types/node@22.19.2)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2) - optionalDependencies: - '@storybook/test': 8.6.14(storybook@8.6.14(prettier@3.7.4)) - transitivePeerDependencies: - - rollup - - supports-color - - typescript - '@storybook/react-vite@8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.7.4)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.53.3)(storybook@8.6.14(prettier@3.7.4))(typescript@5.6.3)(vite@6.4.1(@types/node@22.19.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2))': dependencies: '@joshwooding/vite-plugin-react-docgen-typescript': 0.5.0(typescript@5.6.3)(vite@6.4.1(@types/node@22.19.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2)) @@ -9247,19 +9272,19 @@ snapshots: dependencies: xlsx: 0.18.5 - '@typescript-eslint/eslint-plugin@8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.6.3))(eslint@9.39.2(jiti@1.21.7))(typescript@5.6.3)': + '@typescript-eslint/eslint-plugin@8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.6.3) + '@typescript-eslint/parser': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.49.0 - '@typescript-eslint/type-utils': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.6.3) - '@typescript-eslint/utils': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.6.3) + '@typescript-eslint/type-utils': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/utils': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.49.0 eslint: 9.39.2(jiti@1.21.7) ignore: 7.0.5 natural-compare: 1.4.0 - ts-api-utils: 2.1.0(typescript@5.6.3) - typescript: 5.6.3 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -9295,15 +9320,15 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.6.3)': + '@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)': dependencies: '@typescript-eslint/scope-manager': 8.49.0 '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.6.3) + '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.49.0 debug: 4.4.3 eslint: 9.39.2(jiti@1.21.7) - typescript: 5.6.3 + typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -9362,15 +9387,15 @@ snapshots: dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.6.3)': + '@typescript-eslint/type-utils@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)': dependencies: '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.6.3) - '@typescript-eslint/utils': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.6.3) + '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) debug: 4.4.3 eslint: 9.39.2(jiti@1.21.7) - ts-api-utils: 2.1.0(typescript@5.6.3) - typescript: 5.6.3 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -9430,14 +9455,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.6.3)': + '@typescript-eslint/utils@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@1.21.7)) '@typescript-eslint/scope-manager': 8.49.0 '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.6.3) + '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) eslint: 9.39.2(jiti@1.21.7) - typescript: 5.6.3 + typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -9527,18 +9552,6 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true - '@vitejs/plugin-react@4.7.0(vite@6.4.1(@types/node@22.19.2)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2))': - dependencies: - '@babel/core': 7.28.5 - '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.5) - '@rolldown/pluginutils': 1.0.0-beta.27 - '@types/babel__core': 7.20.5 - react-refresh: 0.17.0 - vite: 6.4.1(@types/node@22.19.2)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2) - transitivePeerDependencies: - - supports-color - '@vitejs/plugin-react@4.7.0(vite@6.4.1(@types/node@22.19.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2))': dependencies: '@babel/core': 7.28.5 @@ -10579,6 +10592,26 @@ snapshots: - eslint-plugin-import-x - supports-color + eslint-config-next@15.4.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3): + dependencies: + '@next/eslint-plugin-next': 15.4.0 + '@rushstack/eslint-patch': 1.15.0 + '@typescript-eslint/eslint-plugin': 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/parser': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + eslint: 9.39.2(jiti@1.21.7) + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-react: 7.37.5(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-react-hooks: 5.2.0(eslint@9.39.2(jiti@1.21.7)) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - eslint-import-resolver-webpack + - eslint-plugin-import-x + - supports-color + eslint-config-next@15.4.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): dependencies: '@next/eslint-plugin-next': 15.4.0 @@ -10607,6 +10640,21 @@ snapshots: transitivePeerDependencies: - supports-color + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@1.21.7)): + dependencies: + '@nolyfill/is-core-module': 1.0.39 + debug: 4.4.3 + eslint: 9.39.2(jiti@1.21.7) + get-tsconfig: 4.13.0 + is-bun-module: 2.0.0 + stable-hash: 0.0.5 + tinyglobby: 0.2.15 + unrs-resolver: 1.11.1 + optionalDependencies: + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@1.21.7)) + transitivePeerDependencies: + - supports-color + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.6.1)): dependencies: '@nolyfill/is-core-module': 1.0.39 @@ -10622,6 +10670,17 @@ snapshots: transitivePeerDependencies: - supports-color + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@1.21.7)): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + eslint: 9.39.2(jiti@1.21.7) + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@1.21.7)) + transitivePeerDependencies: + - supports-color + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)): dependencies: debug: 3.2.7 @@ -10633,6 +10692,35 @@ snapshots: transitivePeerDependencies: - supports-color + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@1.21.7)): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.9 + array.prototype.findlastindex: 1.2.6 + array.prototype.flat: 1.3.3 + array.prototype.flatmap: 1.3.3 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 9.39.2(jiti@1.21.7) + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@1.21.7)) + hasown: 2.0.2 + is-core-module: 2.16.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.1 + semver: 6.3.1 + string.prototype.trimend: 1.0.9 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)): dependencies: '@rtsao/scc': 1.1.0 @@ -10662,6 +10750,25 @@ snapshots: - eslint-import-resolver-webpack - supports-color + eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.2(jiti@1.21.7)): + dependencies: + aria-query: 5.3.2 + array-includes: 3.1.9 + array.prototype.flatmap: 1.3.3 + ast-types-flow: 0.0.8 + axe-core: 4.11.0 + axobject-query: 4.1.0 + damerau-levenshtein: 1.0.8 + emoji-regex: 9.2.2 + eslint: 9.39.2(jiti@1.21.7) + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + language-tags: 1.0.9 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + safe-regex-test: 1.1.0 + string.prototype.includes: 2.0.1 + eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.2(jiti@2.6.1)): dependencies: aria-query: 5.3.2 @@ -10689,14 +10796,32 @@ snapshots: dependencies: eslint: 9.39.2(jiti@2.6.1) - eslint-plugin-react-refresh@0.4.24(eslint@9.39.2(jiti@1.21.7)): - dependencies: - eslint: 9.39.2(jiti@1.21.7) - eslint-plugin-react-refresh@0.4.24(eslint@9.39.2(jiti@2.6.1)): dependencies: eslint: 9.39.2(jiti@2.6.1) + eslint-plugin-react@7.37.5(eslint@9.39.2(jiti@1.21.7)): + dependencies: + array-includes: 3.1.9 + array.prototype.findlast: 1.2.5 + array.prototype.flatmap: 1.3.3 + array.prototype.tosorted: 1.1.4 + doctrine: 2.1.0 + es-iterator-helpers: 1.2.1 + eslint: 9.39.2(jiti@1.21.7) + estraverse: 5.3.0 + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.2 + object.entries: 1.1.9 + object.fromentries: 2.0.8 + object.values: 1.2.1 + prop-types: 15.8.1 + resolve: 2.0.0-next.5 + semver: 6.3.1 + string.prototype.matchall: 4.0.12 + string.prototype.repeat: 1.0.0 + eslint-plugin-react@7.37.5(eslint@9.39.2(jiti@2.6.1)): dependencies: array-includes: 3.1.9 @@ -10719,16 +10844,6 @@ snapshots: string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 - eslint-plugin-storybook@0.11.6(eslint@9.39.2(jiti@1.21.7))(typescript@5.6.3): - dependencies: - '@storybook/csf': 0.1.13 - '@typescript-eslint/utils': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.6.3) - eslint: 9.39.2(jiti@1.21.7) - ts-dedent: 2.2.0 - transitivePeerDependencies: - - supports-color - - typescript - eslint-plugin-storybook@0.11.6(eslint@9.39.2(jiti@2.6.1))(typescript@5.6.3): dependencies: '@storybook/csf': 0.1.13 @@ -12804,33 +12919,6 @@ snapshots: tslib@2.8.1: {} - tsup@8.3.6(jiti@1.21.7)(postcss@8.5.6)(typescript@5.6.3)(yaml@2.8.2): - dependencies: - bundle-require: 5.1.0(esbuild@0.24.2) - cac: 6.7.14 - chokidar: 4.0.3 - consola: 3.4.2 - debug: 4.4.3 - esbuild: 0.24.2 - joycon: 3.1.1 - picocolors: 1.1.1 - postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.6)(yaml@2.8.2) - resolve-from: 5.0.0 - rollup: 4.53.3 - source-map: 0.7.6 - sucrase: 3.35.1 - tinyexec: 0.3.2 - tinyglobby: 0.2.15 - tree-kill: 1.2.2 - optionalDependencies: - postcss: 8.5.6 - typescript: 5.6.3 - transitivePeerDependencies: - - jiti - - supports-color - - tsx - - yaml - tsup@8.3.6(jiti@2.6.1)(postcss@8.5.6)(typescript@5.6.3)(yaml@2.8.2): dependencies: bundle-require: 5.1.0(esbuild@0.24.2) @@ -12961,17 +13049,6 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript-eslint@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.6.3): - dependencies: - '@typescript-eslint/eslint-plugin': 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.6.3))(eslint@9.39.2(jiti@1.21.7))(typescript@5.6.3) - '@typescript-eslint/parser': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.6.3) - '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.6.3) - '@typescript-eslint/utils': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.6.3) - eslint: 9.39.2(jiti@1.21.7) - typescript: 5.6.3 - transitivePeerDependencies: - - supports-color - typescript-eslint@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.6.3): dependencies: '@typescript-eslint/eslint-plugin': 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.6.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.6.3) @@ -13098,17 +13175,6 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 - vite-tsconfig-paths@5.1.4(typescript@5.6.3)(vite@6.4.1(@types/node@22.19.2)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2)): - dependencies: - debug: 4.4.3 - globrex: 0.1.2 - tsconfck: 3.1.6(typescript@5.6.3) - optionalDependencies: - vite: 6.4.1(@types/node@22.19.2)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2) - transitivePeerDependencies: - - supports-color - - typescript - vite-tsconfig-paths@5.1.4(typescript@5.6.3)(vite@6.4.1(@types/node@22.19.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2)): dependencies: debug: 4.4.3 @@ -13131,22 +13197,6 @@ snapshots: lightningcss: 1.30.2 terser: 5.44.1 - vite@6.4.1(@types/node@22.19.2)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2): - dependencies: - esbuild: 0.25.12 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - postcss: 8.5.6 - rollup: 4.53.3 - tinyglobby: 0.2.15 - optionalDependencies: - '@types/node': 22.19.2 - fsevents: 2.3.3 - jiti: 1.21.7 - lightningcss: 1.30.2 - terser: 5.44.1 - yaml: 2.8.2 - vite@6.4.1(@types/node@22.19.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2): dependencies: esbuild: 0.25.12