diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 6e655bf7a..def98ad41 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -1,213 +1,75 @@ # CLAUDE.md -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +## 프로젝트 개요 -## Project Overview +KOIN은 한국기술교육대학교(KOREATECH) 재학생을 위한 캠퍼스 서비스 웹앱이다. 시간표, 버스, 식당, 가게, 커뮤니티, 동아리, 분실물, 졸업 계산기를 제공한다. -KOIN is a campus service web application for Korea University of Technology and Education (KOREATECH). It provides timetable management, bus schedules, cafeteria menus, store/shop listings, community articles, clubs, lost & found, and graduation calculators. +**기술 스택:** Next.js 15 (Pages Router) · React 19 · TypeScript strict · Yarn 4 (Berry) PnP · Node 20.11.1 -Built with **Next.js 15 (Pages Router)**, **React 19**, and **TypeScript** (strict mode). - -- Package manager: **Yarn 4 (Berry)** with PnP. Never use `npm install`. -- Node version: **20.11.1** - -## Commands +## 주요 명령어 ```bash -yarn start # Dev server (next dev) -yarn build # Type-check (tsc) then production build +yarn start # 개발 서버 (next dev) +yarn build # 타입 체크(tsc) + 프로덕션 빌드 yarn lint # ESLint + Stylelint -yarn lint:eslint # ESLint only (src/) -yarn lint:stylelint # Stylelint only (src/**/*.scss) -yarn log # Generate analytics logging hooks from Notion spec -``` - -## Architecture - -### Routing (Pages Router) - -File-based routing in `src/pages/`. Type-safe route builder in `src/static/routes.ts`: - -```typescript -ROUTES.StoreDetail({ id: '123' }); // → '/store/123' -``` - -Pages can declare static properties: - -```typescript -Page.getLayout = (page: React.ReactNode) => {page}; -Page.requireAuth = true; -Page.title = 'Page Title'; -``` - -### API Layer - -`src/api/` uses a class-based pattern with `APIClient` wrapper (`src/utils/ts/apiClient.ts`): - -1. Define request class in `APIDetail.ts` implementing `APIRequest` -2. Define request/response types in `entity.ts` -3. Export callable function via `APIClient.of(DetailClass)` in `index.ts` - -```typescript -export class Login implements APIRequest { - path = '/user/login'; - method = HTTP_METHOD.POST; - data: LoginRequest; -} -// index.ts -export const login = APIClient.of(Login); +yarn lint:eslint # ESLint (src/) +yarn lint:stylelint # Stylelint (src/**/*.scss) +yarn log # Notion 스펙에서 분석 로깅 훅 생성 ``` -The APIClient handles token refresh (401), maintenance mode (503), and user type verification (403) automatically. - -### State Management - -- **Server state**: TanStack React Query (`staleTime: 60000`, `retry: false`). SSR via `getServerSideProps` + `HydrationBoundary`. -- **Client state**: Zustand stores in `src/utils/zustand/`. Many stores separate `State` and `Actions` types — follow the existing pattern of each file. - -Zustand stores export selectors: - -```typescript -export const useStateSelector = () => useStore((state) => state.prop); -export const useActions = () => useStore((state) => state.action); -``` - -### Styling - -SCSS with CSS Modules (`[Component].module.scss`) using BEM methodology. Desktop-first approach with mobile overrides via responsive mixins in `src/utils/scss/`: - -```scss -@include media.media-breakpoint(mobile) { - /* breakpoint: 576px */ -} -``` - -### Component Organization - -Feature-based: `src/components/[Feature]/` with co-located hooks in `hooks/` subdirectory and styles. Split responsive views into `MobileView/` and `PCView/` directories when layouts differ. Shared UI in `src/components/ui/`, layouts in `src/components/layout/`, modals in `src/components/modal/`. - -### Layout - -Two layout components in `src/components/layout/`: - -- **`SSRLayout`**: No Suspense wrapping. Used for SSR pages via `getLayout`. -- **`Layout`** (default): Wraps Header/children in Suspense boundaries. Hides Footer in native WebView. +**패키지 매니저:** Yarn 4만 사용. `npm install` 금지. -Misusing these causes hydration errors. +## 아키텍처 핵심 포인터 -### Custom Hooks - -Located in `src/utils/hooks/`, organized by category: - -- `auth/` — useAuth, useAutoLogin, useLoginRedirect, useLogout -- `ui/` — useBodyScrollLock, useOutsideClick, useEscapeKeyDown -- `state/` — useBooleanState, useLocalStorage, useWebStorage, useMount -- `layout/` — useMediaQuery, useModalPortal -- `analytics/` — useLogger, useScrollLogging - -### Internal Packages - -- **`@bcsdlab/koin`**: `isKoinError()` type guard, `sendClientError()` (sends errors to internal Slack). -- **`@bcsdlab/utils`**: `cn()` (className merger), `sha256()` (Web Crypto hashing). - -These are internal BCSD Lab packages — do not suggest replacing them with external alternatives. - -### Cookie Management - -Cookie keys are environment-aware via `IS_STAGE` flag in `src/static/url.ts`. Stage and production use different cookie key names (e.g., `STAGE_AUTH_TOKEN_KEY` vs `AUTH_TOKEN_KEY`) and different domains (`.stage.koreatech.in` vs `.koreatech.in`). Always use `COOKIE_KEY` constants and `getCookieDomain()` — never hard-code cookie names or domains. - -### iOS Native Bridge - -WebKit message handlers for token sync between web and native app via `window.webkit.messageHandlers`. Bridge functions in `src/utils/ts/iosBridge.ts`. - -### Analytics - -Generated logging hooks in `src/generated/analytics/` from Notion spec via `yarn log`. Google Analytics + GTM + Sentry error tracking. - -## Code Conventions - -### Imports - -Absolute imports via `*` → `src/*` path mapping. Use `import X from 'components/...'` not `'../../../components/...'`. Relative parent imports (`../*`) are forbidden by ESLint. - -Import order (enforced): React/Next → builtins → external packages → internal (`@/**`) → parent/sibling → types → styles (`.scss`). - -### Naming - -- Components/directories: PascalCase (`ArticleList.tsx`) -- Utilities: camelCase (`apiClient.ts`) -- Hooks: `use[Name].ts` -- Styles: `[Component].module.scss` -- API types: `entity.ts`, API classes: `APIDetail.ts` - -### Formatting - -Prettier: 120 char width, single quotes, trailing commas, 2-space indent. Stylelint enforces `stylelint-config-standard-scss`. - -## PR Review Rules (for claude-code-action) - -Write all review comments in Korean. - -Focus on correctness, regression risk, security, and performance before style. - -Use this output format for every finding: - -- Severity: `[P0]` (blocks merge), `[P1]` (should fix), `[P2]` (suggestion) -- Location: `file:line` -- Why it matters -- Minimal fix suggestion - -### Error Handling - -- Always use `isKoinError()` type guard before accessing error properties on API errors. -- In ErrorBoundary, use `isAxiosError()` type guard to branch handling. - -```typescript -onError: (error) => { - if (isKoinError(error)) { - showToast('error', error.message || 'fallback message'); - } else { - showToast('error', 'fallback message'); - } -}; -``` +| 영역 | 패턴 요약 | 위치 | +|------|-----------|------| +| 라우팅 | Pages Router, `ROUTES` 헬퍼 사용 | `src/static/routes.ts` | +| API | `APIRequest` 클래스 → `APIClient.of()` export | `src/api/[domain]/` | +| 서버 상태 | React Query (`staleTime: 60000, retry: false`) | `src/api/[domain]/queries.ts` | +| 클라이언트 상태 | Zustand (`State`/`Actions` 타입 분리) | `src/utils/zustand/` | +| 스타일 | SCSS Modules + BEM, 데스크톱 우선 | `[Component].module.scss` | +| 레이아웃 | SSR 페이지 → `SSRLayout`, 클라이언트 → `Layout` | `src/components/layout/` | +| 내부 패키지 | `@bcsdlab/koin` (isKoinError, sendClientError), `@bcsdlab/utils` (cn, sha256) | 교체 금지 | -### React Query +## 필수 준수 규칙 -- Query keys as arrays: `['resource', 'action', params]`. -- Prefer `useSuspenseQuery` when a Suspense boundary wraps the component for blocking UI. Use `useQuery` for conditional fetching (`enabled`), background refresh, or non-blocking patterns. -- Invalidate cache via `queryClient.invalidateQueries()` after mutations. -- Every mutation `onError` must follow the error handling pattern above. +1. **임포트:** 절대 경로 사용 (`import X from 'components/...'`). 상위 경로(`../`) ESLint 금지. +2. **에러 핸들링:** mutation `onError`에 반드시 `isKoinError()` 타입 가드 → `showToast()` 패턴 적용. +3. **쿠키:** `COOKIE_KEY` 상수 + `getCookieDomain()` 사용. 쿠키명/도메인 하드코딩 금지. +4. **SSR 안전성:** `window`/`document`/`localStorage` 접근 시 `typeof window !== 'undefined'` 체크 필수. +5. **라우팅:** `ROUTES` 헬퍼 사용. 경로 문자열 하드코딩 금지. +6. **로깅:** `console.log` 금지. `console.warn`/`console.error`만 허용. +7. **토스트:** `showToast(type, message)` 사용. `toast()` 직접 호출 금지. +8. **iOS 브릿지:** `window.webkit` optional chaining 유지 (`src/utils/ts/iosBridge.ts`). +9. **분석 로깅:** 주요 사용자 인터랙션마다 `useLogger()` 훅 포함. -### Analytics Logging +## PR 리뷰 규칙 (claude-code-action용) -- All user interactions (click, swipe, page load) must include logging. -- Use the `useLogger()` hook with `team`, `event_label`, `value` structure. -- Define logging constants (`loggingTitle`, `loggingValue`) at the top of the component. +리뷰 댓글은 **한국어**로 작성. 정확성 > 회귀 위험 > 보안 > 성능 > 스타일 순으로 검토. -### General +**출력 형식 (모든 발견에 적용):** +- 심각도: `[P0]` (머지 차단) · `[P1]` (수정 필요) · `[P2]` (제안) +- 위치: `file:line` +- 영향과 최소 수정안 -- No `console.log` (ESLint warn). Only `console.warn` and `console.error` allowed. -- Import SVGs as React components via `@svgr/webpack`. -- Use `showToast(type, message)` utility instead of calling `toast()` directly. Type: `'success' | 'error' | 'info' | 'warning' | 'default'`. +**검토 제외:** +- lint/import-order 포매팅 노이즈 +- 자동 생성 파일만 변경 (`src/generated/**`, `analytics.events.json`) +- `@bcsdlab/koin`, `@bcsdlab/utils` 교체 제안 -### Project-Specific Must-Checks +## 유효성 검사 정책 -- **SSR safety**: Guard `window`, `document`, `localStorage`, `sessionStorage` with browser checks. Verify correct layout usage (`SSRLayout` for SSR pages, `Layout` for client pages). -- **React Query SSR hydration**: Verify prefetch query keys and hydration state keys are consistent. -- **Auth/API stability**: Do not break token refresh lock, 401/403/503 handling, or retry flow. -- **Cookie safety**: Must use `COOKIE_KEY` constants and `getCookieDomain()` — never hard-code cookie names or domains. Verify stage/production separation is preserved. -- **iOS bridge stability**: Preserve `window.webkit` optional chaining and native callback contract. -- **Routing consistency**: Prefer `ROUTES` helpers over hard-coded path strings. -- **Next.js performance**: Flag async waterfalls, unnecessary heavy static imports, and missed dynamic imports. +- 기본: `yarn lint` +- `yarn build` 실패는 환경 제약(API 접근 불가 등)이 원인일 수 있으므로, 변경 코드가 직접 원인이 아니면 비차단 처리. -### Validation Policy +## 하네스: KOIN AI 파이프라인 -- Primary check: `yarn lint`. -- `yarn build` may fail due to environment constraints (missing API access, sandbox limitations); treat as non-blocking unless the changed code directly caused the failure. +**목표:** SonarCloud 코드 품질 분석 및 자동 수정, 기능 구현 자동화 PR 생성 -### Do Not Review +**트리거:** "SonarCloud 분석", "SonarCloud 이슈 수정", "코드 품질 개선", "기능 구현해줘", "새 기능 추가해줘", "PR 만들어줘", "파이프라인 실행", "다시 실행", "재실행", "이전 결과 개선", "업데이트" 등의 요청 시 `koin-pipeline` 스킬을 사용하라. -- Pure formatting/import-order noise already covered by lint. -- Generated artifacts only (`analytics.events.json`, `src/generated/**`) unless generation logic changed. -- `@bcsdlab/koin` and `@bcsdlab/utils` are internal packages — do not suggest external replacements. +**변경 이력:** +| 날짜 | 변경 내용 | 대상 | 사유 | +|------|----------|------|------| +| 2026-04-21 | 초기 구성 | 전체 | SonarCloud 품질 관리 + 기능 구현 자동화 파이프라인 | +| 2026-04-21 | 전체 재작성 | CLAUDE.md | 영어→한국어, 스킬 중복 제거, 분량 축소 (224줄→80줄) | diff --git a/.claude/agents/code-quality-fixer.md b/.claude/agents/code-quality-fixer.md new file mode 100644 index 000000000..f71c86502 --- /dev/null +++ b/.claude/agents/code-quality-fixer.md @@ -0,0 +1,58 @@ +--- +name: code-quality-fixer +description: SonarCloud 이슈 분석 결과를 바탕으로 KOIN 프로젝트의 코드 품질 이슈를 실제로 수정하는 에이전트. TypeScript 타입 오류, React 패턴 개선, 보안 이슈를 KOIN 컨벤션에 맞게 수정한다. +model: sonnet +--- + +# Code Quality Fixer + +## 핵심 역할 + +sonarcloud-analyzer가 생성한 이슈 배치를 처리하여, KOIN 프로젝트 컨벤션에 맞게 코드를 수정한다. `code-quality-fix` 스킬의 가이드를 따른다. + +## 작업 원칙 + +1. 반드시 `code-quality-fix` 스킬을 로딩하여 KOIN 컨벤션과 수정 패턴을 확인한다. +2. 수정 전 대상 파일을 먼저 읽고, 변경 범위를 최소화한다. +3. 수정은 이슈 단위로 진행하며, 각 수정 후 변경 내용을 기록한다. +4. `yarn lint:eslint`가 통과하는 수정만 최종 반영한다. +5. 기존 기능을 깨지 않는 수정만 한다 — 비즈니스 로직 변경 금지. +6. KOIN 프로젝트 특수 규칙을 반드시 지킨다: + - `isKoinError()` 타입 가드 사용 + - `COOKIE_KEY` 상수 사용 (하드코딩 금지) + - `ROUTES` 헬퍼 사용 (경로 하드코딩 금지) + - SSR 안전성 보장 (`window`, `localStorage` 접근 시 브라우저 체크) + +## 입력 + +- `_workspace/01_analyzer_issues.json` (sonarcloud-analyzer 출력) +- 처리할 배치 번호 (없으면 배치 1부터 순차 처리) + +## 출력 + +`_workspace/02_fixer_result.json` 파일: +```json +{ + "fixed": [ + { + "file": "src/components/...", + "issue_key": "...", + "description": "수정 내용 요약" + } + ], + "failed": [], + "skipped": [], + "changed_files": ["src/components/...", "..."] +} +``` + +## 에러 핸들링 + +- 파일을 찾을 수 없을 때: skipped 목록에 추가, 계속 진행 +- 수정 후 lint 실패 시: 원본으로 되돌리고 failed 목록에 추가 +- 타입 에러 발생 시: 수정 취소하고 수동 검토 필요 표시 + +## 협업 + +- **입력 출처**: sonarcloud-analyzer (`_workspace/01_analyzer_issues.json`) +- **출력 대상**: pr-creator (`_workspace/02_fixer_result.json` + 실제 수정된 파일) diff --git a/.claude/agents/feature-implementer.md b/.claude/agents/feature-implementer.md new file mode 100644 index 000000000..49b845f23 --- /dev/null +++ b/.claude/agents/feature-implementer.md @@ -0,0 +1,77 @@ +--- +name: feature-implementer +description: feature-planner의 구현 계획을 바탕으로 KOIN 프로젝트 컨벤션에 맞게 실제 코드를 작성하는 에이전트. TypeScript, React, SCSS, React Query, Zustand 패턴을 정확히 따른다. +model: opus +--- + +# Feature Implementer + +## 핵심 역할 + +`_workspace/01_planner_plan.md`에 명시된 구현 계획대로 코드를 작성한다. KOIN 프로젝트의 모든 컨벤션과 패턴을 준수하며, `feature-implementation` 스킬의 가이드를 따른다. + +## 작업 원칙 + +1. `feature-implementation` 스킬을 반드시 로딩하여 KOIN 패턴을 확인한다. +2. 계획에 명시된 참조 케이스 파일을 읽고 패턴을 그대로 따른다. +3. 파일 생성 순서: entity.ts → APIDetail.ts → index.ts → hooks → components → pages +4. 각 파일 작성 후 `yarn lint:eslint` 통과를 확인한다 (전체 완성 전에도). +5. 절대 경로 임포트를 사용한다 (`components/...`, `utils/...`). +6. 분석 로깅을 포함한다 — 모든 주요 사용자 인터랙션에 `useLogger()` 훅 추가. +7. SSR 안전성을 항상 확보한다: + - `window`, `document` 접근 시 `typeof window !== 'undefined'` 체크 + - SSR 페이지에는 `SSRLayout`, 클라이언트 페이지에는 `Layout` 사용 + +## KOIN 필수 패턴 + +```typescript +// API 클래스 +export class GetFeatureList implements APIRequest { + path = '/feature'; + method = HTTP_METHOD.GET; + auth = false; +} + +// React Query +const { data } = useSuspenseQuery({ + queryKey: ['feature', 'list'], + queryFn: () => getFeatureList(), +}); + +// 에러 핸들링 +onError: (error) => { + if (isKoinError(error)) { + showToast('error', error.message || '오류가 발생했습니다.'); + } else { + showToast('error', '오류가 발생했습니다.'); + } +} +``` + +## 입력 + +- `_workspace/01_planner_plan.md` (feature-planner 출력) + +## 출력 + +`_workspace/02_implementer_result.json` 파일: +```json +{ + "branch": "feature/[name]", + "created_files": ["src/api/...", "src/components/..."], + "modified_files": ["src/pages/..."], + "lint_passed": true, + "notes": "구현 중 발견된 특이사항" +} +``` ++ 실제 구현된 소스 파일들 + +## 에러 핸들링 + +- 계획에 없는 파일이 필요할 때: 해당 내용을 notes에 기록하고 계속 진행 +- lint 실패 시: 즉시 수정 후 재확인, 3회 실패 시 중단하고 이슈 보고 + +## 협업 + +- **입력 출처**: feature-planner (`_workspace/01_planner_plan.md`) +- **출력 대상**: qa-reviewer, pr-creator (`_workspace/02_implementer_result.json`) diff --git a/.claude/agents/feature-planner.md b/.claude/agents/feature-planner.md new file mode 100644 index 000000000..71cb2898e --- /dev/null +++ b/.claude/agents/feature-planner.md @@ -0,0 +1,70 @@ +--- +name: feature-planner +description: 기능 구현 요구사항을 분석하고, KOIN 프로젝트 구조에 맞는 구현 계획을 수립하는 에이전트. API 설계, 컴포넌트 구조, 상태 관리 전략, 파일 목록을 결정한다. +model: opus +--- + +# Feature Planner + +## 핵심 역할 + +사용자의 기능 요구사항을 KOIN 프로젝트 아키텍처에 맞게 분해하고, feature-implementer가 코드를 작성할 수 있는 상세 구현 계획을 만든다. + +## 작업 원칙 + +1. `feature-planning` 스킬을 로딩하여 KOIN 아키텍처 패턴을 참조한다. +2. 기존 코드베이스를 먼저 탐색하여 재사용 가능한 컴포넌트와 패턴을 파악한다. +3. 유사한 기능이 이미 있다면 해당 구현을 참조 케이스로 포함한다. +4. API 엔드포인트가 필요하면 `src/api/` 패턴에 맞는 APIDetail.ts 설계를 포함한다. +5. 계획은 구체적이어야 한다: 파일명, 컴포넌트명, 훅명, 타입명을 명시한다. +6. 브랜치 전략을 결정한다: `develop`에서 분기, 브랜치명 규칙 `feature/[기능명]`. + +## 입력 + +- 기능 요구사항 (자연어 또는 이슈/티켓 내용) +- (선택) 참조할 기존 기능 경로 + +## 출력 + +`_workspace/01_planner_plan.md` 파일: +```markdown +# 기능 구현 계획: [기능명] + +## 개요 +[기능 설명 1~2줄] + +## 브랜치 +- 베이스: develop +- 브랜치명: feature/[name] + +## 구현 파일 목록 +### 신규 생성 +- src/api/[domain]/APIDetail.ts — [설명] +- src/api/[domain]/entity.ts — [타입 정의] +- ... + +### 수정 +- src/pages/[path].tsx — [변경 내용] +- ... + +## API 설계 +[APIDetail.ts 클래스 설계] + +## 컴포넌트 구조 +[컴포넌트 트리] + +## 상태 관리 전략 +[React Query / Zustand 사용 결정] + +## 참조 케이스 +[유사 기능 파일 경로] +``` + +## 에러 핸들링 + +- 요구사항이 불명확할 때: 구체적인 질문 목록을 생성하고 사용자 확인 요청 +- 기존 기능과 충돌 가능성이 있을 때: 위험 구간을 계획에 명시 + +## 협업 + +- **출력 대상**: feature-implementer (`_workspace/01_planner_plan.md`) diff --git a/.claude/agents/pr-creator.md b/.claude/agents/pr-creator.md new file mode 100644 index 000000000..6f4a9aab8 --- /dev/null +++ b/.claude/agents/pr-creator.md @@ -0,0 +1,93 @@ +--- +name: pr-creator +description: 코드 변경사항을 브랜치에 커밋하고 GitHub Pull Request를 생성하는 에이전트. SonarCloud 품질 개선 PR과 기능 구현 PR 두 가지 용도로 사용된다. +model: haiku +--- + +# PR Creator + +## 핵심 역할 + +QA 검증이 완료된 코드 변경사항을 git 브랜치에 커밋하고, GitHub PR을 생성한다. `github-pr-creation` 스킬의 가이드를 따른다. + +## 작업 원칙 + +1. `github-pr-creation` 스킬을 로딩하여 PR 생성 절차를 확인한다. +2. QA 결과(`_workspace/03_qa_result.json`)에서 `approved: true`인 경우에만 PR을 생성한다. +3. 브랜치는 `develop`에서 분기한다. +4. 커밋 메시지는 컨벤셔널 커밋 형식을 따른다. +5. PR 본문은 한국어로 작성한다 (KOIN PR 리뷰 규칙). +6. 기존 PR 체크 후, 동일 브랜치에 PR이 이미 있으면 업데이트하지 않고 링크를 반환한다. + +## 브랜치 네이밍 규칙 + +- 품질 개선: `fix/sonarcloud-quality-[YYYYMMDD]` +- 기능 구현: `feature/[기능명-kebab-case]` + +## PR 템플릿 + +**품질 개선 PR:** +```markdown +## 개요 +SonarCloud 분석 결과를 기반으로 코드 품질 이슈를 수정했습니다. + +## 변경 사항 +- [수정된 이슈 목록] + +## SonarCloud 이슈 통계 +- 수정: N건 +- 건너뜀: N건 + +## 체크리스트 +- [ ] yarn lint 통과 +- [ ] 기능 변경 없음 (품질 개선만) +``` + +**기능 구현 PR:** +```markdown +## 개요 +[기능 설명] + +## 변경 사항 +### 신규 파일 +- [파일 목록] + +### 수정 파일 +- [파일 목록] + +## 테스트 방법 +[수동 테스트 시나리오] + +## 체크리스트 +- [ ] yarn lint 통과 +- [ ] SSR 안전성 확인 +- [ ] 에러 핸들링 패턴 적용 +- [ ] 분석 로깅 포함 +``` + +## 입력 + +- `_workspace/03_qa_result.json` (qa-reviewer 출력) +- `_workspace/02_fixer_result.json` 또는 `_workspace/02_implementer_result.json` +- PR 타입: `quality-fix` 또는 `feature` + +## 출력 + +```json +{ + "pr_url": "https://github.com/BCSDLab/KOIN_WEB_RECODE/pull/NNN", + "branch": "feature/...", + "commit_sha": "abc123" +} +``` + +## 에러 핸들링 + +- QA `approved: false` 시: PR 생성 거부, 미승인 이슈 목록 반환 +- git push 실패 시: 에러 메시지와 수동 해결 방법 제시 +- PR 생성 실패 시: gh CLI 오류 메시지 포함하여 보고 + +## 협업 + +- **입력 출처**: qa-reviewer +- **최종 산출물**: GitHub PR URL diff --git a/.claude/agents/qa-reviewer.md b/.claude/agents/qa-reviewer.md new file mode 100644 index 000000000..895ed667a --- /dev/null +++ b/.claude/agents/qa-reviewer.md @@ -0,0 +1,82 @@ +--- +name: qa-reviewer +description: 코드 품질 수정 또는 기능 구현 결과를 검증하는 에이전트. TypeScript 타입 정합성, KOIN 컨벤션 준수, SSR 안전성, 에러 핸들링 패턴을 경계면 교차 비교로 검증한다. +model: sonnet +--- + +# QA Reviewer + +## 핵심 역할 + +구현된 코드가 KOIN 프로젝트의 품질 기준을 충족하는지 검증한다. 단순 존재 확인이 아니라 **경계면 교차 비교**를 수행한다: API 응답 타입과 프론트 훅의 타입 일치, 컴포넌트 props와 페이지 전달값의 형상 비교. + +## 작업 원칙 + +1. `qa-review` 스킬을 로딩하여 검증 체크리스트를 확인한다. +2. 검증은 전체 완성 후 한 번이 아니라, 수정된 파일 단위로 점진적으로 수행한다. +3. `yarn lint` 실행 결과를 반드시 포함한다. +4. 발견된 이슈는 `.claude/CLAUDE.md`의 `## PR Review Rules` 섹션과 동일한 P0/P1/P2 기준으로 분류한다. 출력 JSON의 `severity` 필드에는 `"[P0]"`, `"[P1]"`, `"[P2]"` 형식을 사용한다. + +## 검증 체크리스트 + +### 타입 정합성 +- [ ] API entity.ts의 응답 타입과 훅에서 사용하는 타입 일치 여부 +- [ ] 컴포넌트 props 타입과 실제 전달값 일치 여부 +- [ ] `undefined`/`null` 처리 누락 없음 + +### KOIN 컨벤션 +- [ ] 절대 경로 임포트 사용 (`../` 금지) +- [ ] `isKoinError()` 타입 가드 사용 +- [ ] `COOKIE_KEY` 상수 사용 +- [ ] `ROUTES` 헬퍼 사용 +- [ ] `console.log` 없음 + +### SSR 안전성 +- [ ] `window`, `document`, `localStorage` 접근 시 브라우저 체크 존재 +- [ ] 올바른 레이아웃 사용 (`SSRLayout` / `Layout`) +- [ ] React Query SSR hydration 키 일치 + +### 에러 핸들링 +- [ ] 모든 mutation `onError`에 `isKoinError()` 패턴 적용 +- [ ] `showToast()` 사용 (직접 `toast()` 호출 금지) + +### 분석 로깅 +- [ ] 주요 사용자 인터랙션에 `useLogger()` 존재 + +## 입력 + +검증할 소스 파일 목록: +- SonarCloud 파이프라인: `_workspace/02_fixer_result.json`의 `changed_files` +- 기능 파이프라인: `_workspace/02_implementer_result.json`의 `created_files` + `modified_files` + +## 출력 + +`_workspace/03_qa_result.json` 파일: +```json +{ + "lint_passed": true, + "issues": [ + { + "severity": "[P1]", + "file": "src/components/...", + "line": 42, + "message": "isKoinError() 타입 가드 누락", + "suggestion": "..." + } + ], + "approved": true, + "summary": "검증 완료. [P0] 이슈 없음, [P1] 이슈 2건 수정 필요" +} +``` + +## 에러 핸들링 + +- P0 이슈 발견 시: `approved: false`로 설정, 오케스트레이터에 수정 요청 +- lint 실패 시: 실패 로그를 이슈 목록에 포함 +- 파일을 찾을 수 없을 때: 해당 파일 건너뛰고 notes에 기록 + +## 협업 + +- **입력 출처**: code-quality-fixer 또는 feature-implementer +- **출력 대상**: pr-creator (`_workspace/03_qa_result.json`) +- P0 이슈 발견 시: 오케스트레이터를 통해 fixer/implementer에 재작업 요청 diff --git a/.claude/agents/sonarcloud-analyzer.md b/.claude/agents/sonarcloud-analyzer.md new file mode 100644 index 000000000..a09969e1a --- /dev/null +++ b/.claude/agents/sonarcloud-analyzer.md @@ -0,0 +1,71 @@ +--- +name: sonarcloud-analyzer +description: SonarCloud 분석 결과를 읽고, 코드 이슈를 우선순위에 따라 분류하는 에이전트. SonarCloud API 호출, 이슈 파싱, 수정 대상 선별을 담당한다. +model: sonnet +--- + +# SonarCloud Analyzer + +## 핵심 역할 + +SonarCloud에서 프로젝트 이슈를 조회하고, 수정 가능한 이슈를 우선순위에 따라 분류하여 code-quality-fixer가 처리할 수 있는 형태로 정리한다. + +## 작업 원칙 + +1. SonarCloud API를 통해 이슈를 조회할 때는 `sonarcloud-analysis` 스킬을 사용한다. +2. 이슈는 심각도(BLOCKER > CRITICAL > MAJOR > MINOR > INFO) 순으로 정렬한다. +3. 자동 수정 불가능한 이슈(보안 취약점, 아키텍처 문제)는 제외하고 보고서에 별도 기록한다. +4. 파일당 이슈 수가 많은 경우, 한 번에 처리할 수 있는 범위(파일 10개 이내)로 배치를 나눈다. +5. TypeScript, React, SCSS 이슈를 구분하여 각각의 수정 전략을 다르게 적용한다. + +## 입력 + +- SonarCloud 조직 키 (환경변수 `SONAR_ORGANIZATION`) +- SonarCloud 프로젝트 키 (환경변수 `SONAR_PROJECT_KEY`) +- SonarCloud 토큰 (환경변수 `SONAR_TOKEN`) +- (선택) 필터 조건: 심각도, 이슈 타입, 파일 경로 패턴 + +## 출력 + +`_workspace/01_analyzer_issues.json` 파일: +```json +{ + "summary": { + "total": 123, + "blocker": 0, + "critical": 5, + "major": 30, + "minor": 88, + "skipped": 12 + }, + "batches": [ + { + "batch_id": 1, + "issues": [ + { + "key": "issue_key", + "severity": "CRITICAL", + "type": "BUG", + "component": "src/components/...", + "line": 42, + "message": "...", + "rule": "typescript:S1234", + "fix_hint": "..." + } + ] + } + ], + "skipped_issues": [] +} +``` + +## 에러 핸들링 + +- SonarCloud API 연결 실패 시: 환경변수 설정 방법을 안내하고 중단 +- 이슈 0개인 경우: 성공 메시지와 함께 빈 배치로 종료 +- API 인증 실패 시: 토큰 재발급 방법 안내 (sonarcloud.io > My Account > Security) + +## 협업 + +- **출력 대상**: code-quality-fixer (배치별 이슈 처리) +- **공유 상태**: `_workspace/01_analyzer_issues.json`을 통해 데이터 전달 diff --git a/.claude/skills/code-quality-fix/SKILL.md b/.claude/skills/code-quality-fix/SKILL.md new file mode 100644 index 000000000..a55aab623 --- /dev/null +++ b/.claude/skills/code-quality-fix/SKILL.md @@ -0,0 +1,131 @@ +--- +name: code-quality-fix +description: SonarCloud 이슈를 KOIN 프로젝트 컨벤션에 맞게 수정하는 스킬. TypeScript 타입 오류, React 패턴 개선, 보안 이슈 수정 방법을 제공한다. SonarCloud 이슈 수정, 코드 품질 개선, 정적 분석 이슈 해결을 요청할 때 반드시 이 스킬을 사용할 것. 새 기능 구현에는 사용하지 않는다. +--- + +# Code Quality Fix Skill + +## KOIN 프로젝트 수정 원칙 + +수정은 항상 최소 범위로 한다 — 이슈를 해결하는 데 필요한 변경만, 그 이상은 하지 않는다. + +## 자주 발생하는 SonarCloud 이슈 수정 패턴 + +### TypeScript 관련 + +**미사용 변수/임포트 제거** +```typescript +// 이전 (typescript:S1128) +import { useState, useEffect } from 'react'; // useEffect 미사용 + +// 수정 +import { useState } from 'react'; +``` + +**타입 단언 대신 타입 가드 사용** +```typescript +// 이전 (typescript:S4325) +const value = someValue as string; + +// 수정 +if (typeof someValue === 'string') { + const value = someValue; +} +``` + +**null 체크 누락** +```typescript +// 이전 +const name = user.profile.name; + +// 수정 +const name = user.profile?.name ?? ''; +``` + +### React 관련 + +**useEffect 의존성 배열 누락** +```typescript +// 이전 +useEffect(() => { + fetchData(id); +}, []); // id 누락 + +// 수정 +useEffect(() => { + fetchData(id); +}, [id]); +``` + +**Key prop 누락** +```tsx +// 이전 +{items.map(item => )} + +// 수정 +{items.map(item => )} +``` + +### KOIN 특수 패턴 적용 + +**에러 핸들링 패턴** +```typescript +// 이전 (패턴 불일치) +onError: (error: unknown) => { + showToast('error', (error as any).message); +} + +// 수정 +onError: (error) => { + if (isKoinError(error)) { + showToast('error', error.message || '오류가 발생했습니다.'); + } else { + showToast('error', '오류가 발생했습니다.'); + } +} +``` + +**console.log 제거** +```typescript +// 이전 +console.log('data:', data); + +// 수정: 제거 (디버깅용 로그는 완전 삭제) +// 에러 추적이 필요하면: console.error('...') 또는 Sentry 사용 +``` + +**하드코딩된 경로 → ROUTES 사용** +```typescript +// 이전 +router.push('/store/123'); + +// 수정 +router.push(ROUTES.StoreDetail({ id: '123' })); +``` + +## lint 실행 방법 + +```bash +# 전체 lint +yarn lint + +# ESLint만 +yarn lint:eslint + +# 특정 파일만 +yarn lint:eslint src/components/Store/StoreDetail.tsx +``` + +## 수정 완료 확인 체크리스트 + +- [ ] `yarn lint:eslint` 통과 +- [ ] TypeScript 타입 오류 없음 (`tsc --noEmit`) +- [ ] 기능 변경 없음 (수정 전후 동작 동일) +- [ ] KOIN 특수 패턴 준수 (isKoinError, ROUTES, COOKIE_KEY) + +## 수정 불가 판단 기준 + +다음 경우 수정을 포기하고 failed 목록에 추가한다: +- 수정이 비즈니스 로직 변경을 요구할 때 +- 타입 구조를 근본적으로 바꿔야 할 때 +- 다른 파일에 연쇄적인 영향이 클 때 diff --git a/.claude/skills/feature-implementation/SKILL.md b/.claude/skills/feature-implementation/SKILL.md new file mode 100644 index 000000000..d3224e8f8 --- /dev/null +++ b/.claude/skills/feature-implementation/SKILL.md @@ -0,0 +1,218 @@ +--- +name: feature-implementation +description: KOIN 프로젝트의 기능 구현 계획을 실제 코드로 작성하는 스킬. TypeScript, React, SCSS, React Query, Zustand 패턴을 정확히 따르며 코드를 생성한다. 기능 구현, 코드 작성, API 연동, 컴포넌트 생성을 요청할 때 반드시 이 스킬을 사용할 것. 단순 버그 수정에는 사용하지 않는다. +--- + +# Feature Implementation Skill + +## 구현 순서 + +의존성 방향에 따라 순서를 지킨다: +1. `entity.ts` — 타입 정의 (다른 파일이 의존) +2. `APIDetail.ts` — API 클래스 +3. `index.ts` — export +4. `queries.ts` / `mutations.ts` — React Query 훅 +5. `hooks/use[Feature].ts` — 비즈니스 로직 훅 +6. `[Component].tsx` + `[Component].module.scss` — UI +7. `src/pages/...` — 페이지 연결 + +## 파일별 코드 패턴 + +### entity.ts +```typescript +export interface FeatureItem { + id: number; + name: string; + createdAt: string; +} + +export interface FeatureListResponse { + items: FeatureItem[]; + totalCount: number; +} + +export interface CreateFeatureRequest { + name: string; +} +``` + +### APIDetail.ts +```typescript +import { APIRequest } from 'utils/ts/apiClient'; +import { HTTP_METHOD } from 'static/httpMethod'; +import { FeatureListResponse, CreateFeatureRequest, FeatureResponse } from './entity'; + +export class GetFeatureList implements APIRequest { + path = '/feature'; + method = HTTP_METHOD.GET; + params: { page?: number; size?: number }; + auth = false; +} + +export class PostFeature implements APIRequest { + path = '/feature'; + method = HTTP_METHOD.POST; + data: CreateFeatureRequest; + auth = true; +} +``` + +### index.ts +```typescript +import { APIClient } from 'utils/ts/apiClient'; +import { GetFeatureList, PostFeature } from './APIDetail'; + +export const getFeatureList = APIClient.of(GetFeatureList); +export const postFeature = APIClient.of(PostFeature); +``` + +### queries.ts (React Query) +```typescript +import { useSuspenseQuery, useQuery } from '@tanstack/react-query'; +import { getFeatureList } from '.'; + +export const useFeatureListQuery = (params: { page?: number }) => + useSuspenseQuery({ + queryKey: ['feature', 'list', params], + queryFn: () => getFeatureList({ params }), + }); +``` + +### mutations.ts +```typescript +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { isKoinError } from '@bcsdlab/koin'; +import { showToast } from 'utils/ts/showToast'; +import { postFeature } from '.'; + +export const useCreateFeatureMutation = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: postFeature, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['feature', 'list'] }); + showToast('success', '생성되었습니다.'); + }, + onError: (error) => { + if (isKoinError(error)) { + showToast('error', error.message || '오류가 발생했습니다.'); + } else { + showToast('error', '오류가 발생했습니다.'); + } + }, + }); +}; +``` + +### 컴포넌트 패턴 +```tsx +import React from 'react'; +import { useLogger } from 'utils/hooks/analytics/useLogger'; +import { useFeatureListQuery } from 'api/feature/queries'; +import styles from './Feature.module.scss'; + +const loggingTitle = { + CLICK_ITEM: 'feature_item_click', +}; + +export default function Feature() { + const { logEvent } = useLogger(); + const { data } = useFeatureListQuery({}); + + const handleItemClick = (id: number) => { + logEvent({ team: 'feature', event_label: loggingTitle.CLICK_ITEM, value: String(id) }); + // 클릭 로직 + }; + + return ( +
+ {data.items.map(item => ( + + ))} +
+ ); +} +``` + +### SCSS 패턴 +```scss +// Feature.module.scss +.container { + display: flex; + flex-direction: column; + gap: 16px; + + @include media.media-breakpoint(mobile) { + gap: 8px; + } +} + +.item { + // BEM: .item__title, .item--active +} +``` + +### 페이지 패턴 +```tsx +// src/pages/feature/index.tsx +import type { NextPage } from 'next'; +import { Suspense } from 'react'; +import Feature from 'components/Feature'; + +const FeaturePage: NextPage = () => ( + 로딩 중...}> + + +); + +export default FeaturePage; +``` + +## 임포트 순서 (ESLint 규칙) +```typescript +// 1. React/Next +import React, { useState } from 'react'; +import type { NextPage } from 'next'; +// 2. 외부 패키지 +import { useMutation } from '@tanstack/react-query'; +// 3. 내부 패키지 +import { isKoinError } from '@bcsdlab/koin'; +// 4. 절대 경로 (부모/형제) +import { useFeatureListQuery } from 'api/feature/queries'; +import styles from './Feature.module.scss'; +``` + +## SSR 구현 패턴 + +SSR이 필요한 페이지의 경우: +```tsx +import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query'; +import { getFeatureList } from 'api/feature'; + +export const getServerSideProps = async () => { + const queryClient = new QueryClient(); + await queryClient.prefetchQuery({ + queryKey: ['feature', 'list', {}], + queryFn: () => getFeatureList({}), + }); + + return { props: { dehydratedState: dehydrate(queryClient) } }; +}; +``` + +## 자주 쓰는 유틸리티 + +```typescript +import { cn } from '@bcsdlab/utils'; // className 병합 +import { ROUTES } from 'static/routes'; // 라우트 +import { showToast } from 'utils/ts/showToast'; // 토스트 +import { isKoinError } from '@bcsdlab/koin'; // 에러 타입 가드 +``` diff --git a/.claude/skills/feature-planning/SKILL.md b/.claude/skills/feature-planning/SKILL.md new file mode 100644 index 000000000..d06f180b8 --- /dev/null +++ b/.claude/skills/feature-planning/SKILL.md @@ -0,0 +1,98 @@ +--- +name: feature-planning +description: KOIN 프로젝트에 새 기능을 추가하기 위한 구현 계획을 수립하는 스킬. 기능 요구사항 분석, API 설계, 컴포넌트 구조 결정, 파일 목록 생성을 담당한다. 새 기능 구현 계획, 기능 설계, 구현 범위 파악을 요청할 때 반드시 이 스킬을 사용할 것. 이미 계획이 있는 구현 작업에는 사용하지 않는다. +--- + +# Feature Planning Skill + +## 도메인 파악 + +새 기능이 어느 도메인에 속하는지 먼저 판단한다: + +| 도메인 | 경로 | 특성 | +|--------|------|------| +| 시간표 | `src/api/timetable/`, `src/pages/timetable/` | 복잡한 상태 관리 (Zustand) | +| 버스 | `src/api/bus/`, `src/pages/bus/` | 실시간 데이터, SSE | +| 식당 | `src/api/dinings/`, `src/pages/cafeteria/` | 날짜 기반 필터링 | +| 커뮤니티 | `src/api/articles/`, `src/pages/articles/` | 인증 필요, 페이지네이션 | +| 가게 | `src/api/store/`, `src/pages/store/` | 검색/필터 | +| 클럽 | `src/api/club/`, `src/pages/clubs/` | 좋아요, 카테고리 | + +## 유사 기능 탐색 + +계획 수립 전에 반드시 유사한 기능을 탐색한다: + +```bash +# 유사 도메인의 파일 구조 확인 +ls src/api/[similar-domain]/ +ls src/components/[SimilarFeature]/ + +# 참조할 패턴 파악 +cat src/api/[similar-domain]/APIDetail.ts +cat src/api/[similar-domain]/queries.ts +``` + +## API 설계 패턴 + +```typescript +// src/api/[domain]/APIDetail.ts +export class GetFeatureList implements APIRequest { + path = '/feature'; + method = HTTP_METHOD.GET; + params: { page: number; size: number }; + auth = false; // 인증 필요 시 true +} + +export class PostFeature implements APIRequest { + path = '/feature'; + method = HTTP_METHOD.POST; + data: CreateFeatureRequest; + auth = true; +} +``` + +## 컴포넌트 구조 설계 + +``` +src/components/[FeatureName]/ +├── index.tsx # 메인 컴포넌트 +├── [FeatureName].module.scss # 스타일 +├── hooks/ +│ └── use[FeatureName].ts # 데이터 페칭 훅 +├── PCView/ # PC 전용 뷰 (레이아웃이 크게 다를 때) +│ └── PCView.tsx +└── MobileView/ # 모바일 전용 뷰 + └── MobileView.tsx +``` + +## 상태 관리 결정 기준 + +| 상황 | 선택 | +|------|------| +| 서버 데이터 조회/캐싱 | React Query (`useSuspenseQuery`) | +| 조건부 페칭, 백그라운드 갱신 | React Query (`useQuery`) | +| 전역 UI 상태 (모달, 사이드바) | Zustand | +| 복잡한 폼 | react-hook-form | +| 단순 컴포넌트 내부 상태 | useState | + +## SSR 페이지 여부 결정 + +다음 조건 중 하나라도 해당하면 SSR: +- SEO가 필요한 공개 페이지 +- 초기 로딩 시 서버 데이터가 필요한 경우 +- 소셜 미디어 공유 지원 필요 + +SSR 페이지는: +- `getServerSideProps` 사용 +- `SSRLayout` 사용 +- `HydrationBoundary`로 React Query 상태 전달 + +## 계획 검증 체크리스트 + +- [ ] 유사 기능 참조 케이스 포함 +- [ ] 모든 신규/수정 파일 명시 +- [ ] API 클래스 설계 포함 +- [ ] 인증 필요 여부 결정 +- [ ] SSR/CSR 여부 결정 +- [ ] 반응형 레이아웃 전략 결정 +- [ ] 분석 로깅 포인트 명시 diff --git a/.claude/skills/github-pr-creation/SKILL.md b/.claude/skills/github-pr-creation/SKILL.md new file mode 100644 index 000000000..a223e3a57 --- /dev/null +++ b/.claude/skills/github-pr-creation/SKILL.md @@ -0,0 +1,116 @@ +--- +name: github-pr-creation +description: KOIN 프로젝트의 코드 변경사항을 git 커밋하고 GitHub Pull Request를 생성하는 스킬. 브랜치 생성, 커밋, gh CLI를 통한 PR 작성을 담당한다. PR 생성, GitHub PR 작성, 브랜치 커밋, 풀 리퀘스트를 요청할 때 반드시 이 스킬을 사용할 것. 코드 작성이나 수정에는 사용하지 않는다. +--- + +# GitHub PR Creation Skill + +## 사전 확인 + +```bash +# 현재 브랜치 확인 +git branch --show-current + +# git 상태 확인 +git status + +# gh CLI 인증 확인 +gh auth status +``` + +## 브랜치 생성 및 전환 + +```bash +# develop에서 분기 +git checkout develop +git pull origin develop + +# 브랜치 생성 +git checkout -b feature/[기능명-kebab-case] +# 또는 +git checkout -b fix/sonarcloud-quality-$(date +%Y%m%d) +``` + +## 커밋 규칙 + +컨벤셔널 커밋 형식: +``` +feat: [기능명] 추가 +fix: [이슈] 수정 +refactor: [대상] 리팩토링 +style: 코드 스타일 수정 (SonarCloud 이슈 해결) +chore: 기타 변경사항 +``` + +```bash +# 특정 파일만 스테이징 (git add -A 금지) +git add src/components/Feature/index.tsx +git add src/api/feature/ + +# 커밋 +git commit -m "feat: 기능명 추가 + +- APIDetail.ts: GetFeatureList, PostFeature 클래스 추가 +- queries.ts: useFeatureListQuery 훅 추가 +- Feature/index.tsx: 목록 컴포넌트 구현" +``` + +## PR 생성 (gh CLI) + +```bash +# PR 생성 +gh pr create \ + --base develop \ + --title "[기능] 기능명 추가" \ + --body "$(cat <<'EOF' +## 개요 +[기능 설명 1~2줄] + +## 변경 사항 +### 신규 파일 +- `src/api/feature/APIDetail.ts` — API 클래스 정의 +- `src/components/Feature/index.tsx` — 목록 컴포넌트 + +### 수정 파일 +- `src/pages/feature/index.tsx` — 컴포넌트 연결 + +## 테스트 방법 +1. `yarn start` 실행 +2. `/feature` 페이지 접근 +3. 목록 조회 확인 + +## 체크리스트 +- [x] yarn lint 통과 +- [x] SSR 안전성 확인 +- [x] 에러 핸들링 패턴 적용 (isKoinError) +- [x] 분석 로깅 포함 + +🤖 Generated with [Claude Code](https://claude.ai/claude-code) +EOF +)" +``` + +## PR 생성 후 + +```bash +# PR URL 확인 +gh pr view --web + +# PR 번호 확인 +gh pr list --state open | head -5 +``` + +## 주의사항 + +- `.env`, `.env.local`, 시크릿 파일 절대 커밋 금지 +- `yarn.lock` 변경 없이 lock 파일만 건드리지 않음 +- `src/generated/` 파일은 변경하지 않음 (자동 생성 파일) +- `_workspace/` 디렉토리는 PR에 포함하지 않음 (`.gitignore`에 추가) + +## 브랜치 정리 + +PR 머지 후: +```bash +git checkout develop +git branch -d feature/[기능명] +``` diff --git a/.claude/skills/koin-pipeline/SKILL.md b/.claude/skills/koin-pipeline/SKILL.md new file mode 100644 index 000000000..581d8c920 --- /dev/null +++ b/.claude/skills/koin-pipeline/SKILL.md @@ -0,0 +1,201 @@ +--- +name: koin-pipeline +description: KOIN 프로젝트의 두 가지 AI 파이프라인을 조율하는 오케스트레이터 스킬. (1) SonarCloud 코드 품질 분석 → 이슈 수정 → PR 생성 파이프라인, (2) 기능 요구사항 분석 → 코드 구현 → 검증 → PR 생성 파이프라인을 자동으로 실행한다. "SonarCloud 분석해줘", "SonarCloud 이슈 수정해줘", "코드 품질 개선해줘", "기능 구현해줘", "새 기능 추가해줘", "PR 만들어줘", "파이프라인 실행", "다시 실행", "재실행", "이전 결과 개선", "업데이트" 등의 요청 시 반드시 이 스킬을 사용할 것. +--- + +# KOIN Pipeline Orchestrator + +## Phase 0: 컨텍스트 확인 + +실행 시작 전 기존 작업 상태를 확인한다: + +```bash +ls _workspace/ 2>/dev/null && echo "기존 작업 있음" || echo "신규 실행" +``` + +| 상태 | 액션 | +|------|------| +| `_workspace/` 없음 | 신규 실행 → Phase 1로 | +| `_workspace/` 있고 부분 수정 요청 | 부분 재실행 → 해당 Phase부터 | +| `_workspace/` 있고 새 입력 | `mv _workspace/ _workspace_prev/` 후 신규 실행 | + +## 파이프라인 선택 + +사용자 요청을 분석하여 파이프라인을 선택한다: + +| 요청 패턴 | 파이프라인 | +|-----------|-----------| +| "SonarCloud", "코드 품질", "정적 분석 이슈" | **파이프라인 A** (품질 개선) | +| "기능 구현", "새 기능", "[기능명] 만들어줘" | **파이프라인 B** (기능 구현) | + +--- + +## 파이프라인 A: SonarCloud 코드 품질 개선 + +**실행 모드:** 서브 에이전트 (순차 파이프라인) + +### A-1. SonarCloud Analyzer 실행 + +``` +Agent( + subagent_type: "general-purpose", + model: "sonnet", + prompt: "sonarcloud-analyzer 에이전트 정의 파일(.claude/agents/sonarcloud-analyzer.md)을 읽고, + sonarcloud-analysis 스킬(.claude/skills/sonarcloud-analysis/SKILL.md)을 사용하여 + SonarCloud 이슈를 조회하고 _workspace/01_analyzer_issues.json을 생성하라. + [사용자 입력: {입력값}]" +) +``` + +A-1 완료 후 → A-2 실행 + +### A-2. Code Quality Fixer 실행 + +``` +Agent( + subagent_type: "general-purpose", + model: "sonnet", + prompt: "code-quality-fixer 에이전트 정의 파일(.claude/agents/code-quality-fixer.md)을 읽고, + code-quality-fix 스킬(.claude/skills/code-quality-fix/SKILL.md)을 사용하여 + _workspace/01_analyzer_issues.json의 이슈를 수정하라. + 수정 완료 후 _workspace/02_fixer_result.json을 생성하라." +) +``` + +A-2 완료 후 → A-3 실행 + +### A-3. QA Reviewer 실행 + +``` +Agent( + subagent_type: "general-purpose", + model: "sonnet", + prompt: "qa-reviewer 에이전트 정의 파일(.claude/agents/qa-reviewer.md)을 읽고, + qa-review 스킬(.claude/skills/qa-review/SKILL.md)을 사용하여 + _workspace/02_fixer_result.json의 changed_files를 검증하라. + _workspace/03_qa_result.json을 생성하라." +) +``` + +QA 결과 확인: +- `approved: true` → A-4 실행 +- `approved: false` (P0 이슈) → A-2로 재실행 요청 + +### A-4. PR Creator 실행 + +``` +Agent( + subagent_type: "general-purpose", + model: "haiku", + prompt: "pr-creator 에이전트 정의 파일(.claude/agents/pr-creator.md)을 읽고, + github-pr-creation 스킬(.claude/skills/github-pr-creation/SKILL.md)을 사용하여 + _workspace/02_fixer_result.json과 _workspace/03_qa_result.json을 바탕으로 + PR 타입 'quality-fix'로 GitHub PR을 생성하라." +) +``` + +--- + +## 파이프라인 B: 기능 구현 자동화 + +**실행 모드:** 서브 에이전트 (순차 파이프라인) + +### B-1. Feature Planner 실행 + +``` +Agent( + subagent_type: "general-purpose", + model: "opus", + prompt: "feature-planner 에이전트 정의 파일(.claude/agents/feature-planner.md)을 읽고, + feature-planning 스킬(.claude/skills/feature-planning/SKILL.md)을 사용하여 + 다음 요구사항의 구현 계획을 수립하라: + [요구사항: {입력값}] + _workspace/01_planner_plan.md를 생성하라." +) +``` + +B-1 완료 후 → **계획 검토 요청** (사용자에게 계획 확인) + +사용자 승인 후 → B-2 실행 + +### B-2. Feature Implementer 실행 + +``` +Agent( + subagent_type: "general-purpose", + model: "opus", + prompt: "feature-implementer 에이전트 정의 파일(.claude/agents/feature-implementer.md)을 읽고, + feature-implementation 스킬(.claude/skills/feature-implementation/SKILL.md)을 사용하여 + _workspace/01_planner_plan.md의 계획대로 코드를 구현하라. + _workspace/02_implementer_result.json을 생성하라." +) +``` + +B-2 완료 후 → B-3 실행 + +### B-3. QA Reviewer 실행 + +``` +Agent( + subagent_type: "general-purpose", + model: "sonnet", + prompt: "qa-reviewer 에이전트 정의 파일(.claude/agents/qa-reviewer.md)을 읽고, + qa-review 스킬(.claude/skills/qa-review/SKILL.md)을 사용하여 + _workspace/02_implementer_result.json의 created_files와 modified_files를 검증하라. + _workspace/03_qa_result.json을 생성하라." +) +``` + +QA 결과 확인: +- `approved: true` → B-4 실행 +- `approved: false` (P0 이슈) → B-2로 재실행 (이슈 목록 전달) + +### B-4. PR Creator 실행 + +``` +Agent( + subagent_type: "general-purpose", + model: "haiku", + prompt: "pr-creator 에이전트 정의 파일(.claude/agents/pr-creator.md)을 읽고, + github-pr-creation 스킬(.claude/skills/github-pr-creation/SKILL.md)을 사용하여 + _workspace/02_implementer_result.json과 _workspace/03_qa_result.json을 바탕으로 + PR 타입 'feature'로 GitHub PR을 생성하라." +) +``` + +--- + +## 에러 핸들링 + +| 상황 | 처리 | +|------|------| +| SonarCloud 연결 실패 | 환경변수 설정 안내 후 중단 | +| 이슈 수정 실패 | failed 목록 보고 후 성공 분만 PR | +| QA P0 이슈 (2회 이상 반복) | 중단하고 수동 수정 요청 | +| PR 생성 실패 | git 명령어와 gh CLI 명령어 수동 실행 안내 | + +## 데이터 흐름 + +``` +_workspace/ +├── 01_analyzer_issues.json (파이프라인 A) 또는 +│ 01_planner_plan.md (파이프라인 B) +├── 02_fixer_result.json (파이프라인 A) 또는 +│ 02_implementer_result.json (파이프라인 B) +└── 03_qa_result.json (공통) +``` + +## 테스트 시나리오 + +### 정상 흐름 (파이프라인 A) +1. `SONAR_HOST_URL`, `SONAR_TOKEN` 환경변수 설정 +2. "SonarCloud 분석해줘" 요청 +3. → 이슈 조회 → 배치 생성 → 코드 수정 → QA → PR 생성 + +### 에러 흐름 (파이프라인 A) +1. `SONAR_TOKEN` 미설정 +2. → Analyzer가 인증 실패 감지 → 설정 방법 안내 → 중단 + +### 정상 흐름 (파이프라인 B) +1. "기능 목록 페이지 만들어줘" 요청 +2. → 계획 수립 → 사용자 승인 → 코드 구현 → QA → PR 생성 diff --git a/.claude/skills/qa-review/SKILL.md b/.claude/skills/qa-review/SKILL.md new file mode 100644 index 000000000..72aed6757 --- /dev/null +++ b/.claude/skills/qa-review/SKILL.md @@ -0,0 +1,118 @@ +--- +name: qa-review +description: KOIN 프로젝트 코드 변경사항의 품질을 검증하는 스킬. TypeScript 타입 정합성, KOIN 컨벤션 준수, SSR 안전성, 에러 핸들링 패턴을 경계면 교차 비교로 검증한다. 코드 검증, QA, 품질 확인, 리뷰, 수정 검토를 요청할 때 반드시 이 스킬을 사용할 것. 초기 코드 작성에는 사용하지 않는다. +--- + +# QA Review Skill + +## 검증 철학 + +단순히 파일이 존재하는지 확인하지 않는다. **경계면을 교차로 검증한다:** +- API entity.ts의 응답 타입 ↔ queries.ts에서 사용하는 타입 일치? +- 컴포넌트 props 타입 ↔ 페이지에서 전달하는 값 일치? +- getServerSideProps의 prefetch 쿼리 키 ↔ 컴포넌트의 useQuery 키 일치? + +## 검증 실행 순서 + +### 1단계: lint 실행 +```bash +yarn lint:eslint +yarn lint:stylelint # SCSS 변경이 있을 때 +``` + +### 2단계: 타입 체크 +```bash +# KOIN 프로젝트는 tsc를 단독 script로 노출하지 않음 +# package.json의 build 스크립트(tsc && next build)를 활용하거나 +# node_modules 내 tsc를 직접 호출 +yarn dlx tsc --noEmit 2>&1 | head -50 +``` + +### 3단계: 코드 패턴 검증 + +변경된 파일을 읽고 다음을 확인한다: + +**에러 핸들링 패턴 확인:** +```bash +# mutation onError에 isKoinError 패턴이 없는 파일 탐색 +grep -n "onError" src/api/[domain]/mutations.ts +# isKoinError가 있는지 확인 +grep -n "isKoinError" src/api/[domain]/mutations.ts +``` + +**console.log 확인:** +```bash +grep -rn "console\.log" src/[changed-path] +``` + +**하드코딩된 경로 확인:** +```bash +grep -n "router\.push('/[^']*')" src/[changed-path] +``` + +**절대 경로 임포트 확인:** +```bash +grep -n "from '\.\./" src/[changed-path] +``` + +**SSR 안전성 확인 (window 접근):** +```bash +grep -n "window\." src/[changed-path] +# typeof window !== 'undefined' 체크 없이 직접 접근하는지 확인 +``` + +## 이슈 분류 기준 + +CLAUDE.md `## PR Review Rules` 섹션의 기준을 그대로 따른다. + +| 등급 | 기준 | 조치 | +|------|------|------| +| `[P0]` | 런타임 에러 유발, SSR 크래시, 타입 불일치로 데이터 오염 | PR 차단 | +| `[P1]` | KOIN 컨벤션 위반 (isKoinError 누락, console.log 잔존, 하드코딩) | 수정 권고 | +| `[P2]` | 코드 개선 제안, 최적화 기회 | 선택적 수정 | + +## 검증 보고서 작성 + +이슈가 없을 때: +```json +{ + "lint_passed": true, + "issues": [], + "approved": true, + "summary": "모든 검증 통과. P0/P1 이슈 없음." +} +``` + +이슈 발견 시: +```json +{ + "lint_passed": true, + "issues": [ + { + "severity": "[P1]", + "file": "src/api/feature/mutations.ts", + "line": 15, + "message": "onError에 isKoinError() 타입 가드 누락", + "suggestion": "if (isKoinError(error)) 패턴 적용" + } + ], + "approved": false, + "summary": "[P1] 이슈 1건 발견. 수정 후 재검증 필요." +} +``` + +## P0 이슈 패턴 예시 + +```typescript +// P0: SSR 크래시 — window 직접 접근 +const width = window.innerWidth; // ❌ + +// 수정 +const width = typeof window !== 'undefined' ? window.innerWidth : 0; // ✅ + +// P0: 타입 불일치 — any 캐스팅 +const item = data as any; // ❌ + +// P0: SSRLayout을 클라이언트 전용 페이지에 사용 +// (Suspense 없이 useQuery를 쓰는 컴포넌트가 SSRLayout 아래 있는 경우) +``` diff --git a/.claude/skills/sonarcloud-analysis/SKILL.md b/.claude/skills/sonarcloud-analysis/SKILL.md new file mode 100644 index 000000000..4b6c53324 --- /dev/null +++ b/.claude/skills/sonarcloud-analysis/SKILL.md @@ -0,0 +1,66 @@ +--- +name: sonarcloud-analysis +description: SonarCloud에서 KOIN 프로젝트 코드 이슈를 조회하고 분류하는 스킬. SonarCloud API 호출, 이슈 파싱, 수정 배치 생성을 수행한다. SonarCloud 분석, 이슈 조회, 코드 품질 확인, 정적 분석 결과 파싱 등을 요청할 때 반드시 이 스킬을 사용할 것. 단순 lint 실행이나 ESLint 관련 작업에는 사용하지 않는다. +--- + +# SonarCloud Analysis Skill + +## 환경 설정 확인 + +SonarCloud를 처음 사용하는 경우 `references/sonarcloud-setup.md`를 읽어 설정 방법을 안내한다. + +필수 환경변수: +```bash +SONAR_ORGANIZATION=your-organization-key # sonarcloud.io 조직 키 +SONAR_PROJECT_KEY=koin_web_recode +SONAR_TOKEN=your-personal-access-token # sonarcloud.io > My Account > Security +``` + +`SONAR_HOST_URL`은 SonarCloud 사용 시 불필요하다 (항상 `https://sonarcloud.io`). + +## 이슈 조회 API + +SonarCloud는 Bearer 토큰 인증을 사용한다. + +```bash +# 전체 이슈 조회 (페이지네이션 포함) +curl -H "Authorization: Bearer $SONAR_TOKEN" \ + "https://sonarcloud.io/api/issues/search?componentKeys=$SONAR_PROJECT_KEY&organization=$SONAR_ORGANIZATION&resolved=false&ps=500" \ + | jq '.' + +# 심각도별 필터링 +curl -H "Authorization: Bearer $SONAR_TOKEN" \ + "https://sonarcloud.io/api/issues/search?componentKeys=$SONAR_PROJECT_KEY&organization=$SONAR_ORGANIZATION&severities=BLOCKER,CRITICAL&resolved=false" + +# 특정 규칙 필터링 +curl -H "Authorization: Bearer $SONAR_TOKEN" \ + "https://sonarcloud.io/api/issues/search?componentKeys=$SONAR_PROJECT_KEY&organization=$SONAR_ORGANIZATION&rules=typescript:S1128" +``` + +## 이슈 분류 기준 + +| 심각도 | 자동 수정 가능 | 우선순위 | +|--------|--------------|--------| +| BLOCKER | 케이스별 판단 | 1순위 | +| CRITICAL | 대부분 가능 | 2순위 | +| MAJOR | 가능 | 3순위 | +| MINOR | 가능 | 4순위 | + +## 자동 수정 불가 이슈 패턴 + +다음 이슈는 `skipped_issues`로 분류한다: +- 보안 핫스팟 (Security Hotspot) — 수동 검토 필요 +- 아키텍처 냄새 (Architecture smell) — 리팩토링 범위 +- 중복 코드 (Duplication) — 구조적 변경 필요 +- 복잡도 초과 (Complexity) — 비즈니스 로직 수정 필요 + +## 배치 생성 규칙 + +파일당 이슈가 몰린 경우를 방지하기 위해: +- 배치당 최대 파일 10개 +- 배치당 최대 이슈 50개 +- 같은 파일의 이슈는 동일 배치에 묶음 + +## 프로젝트 설정 (sonar-project.properties) + +프로젝트 루트에 아직 `sonar-project.properties`가 없다면 `references/sonarcloud-setup.md`를 참조하여 생성한다. diff --git a/.claude/skills/sonarcloud-analysis/references/sonarcloud-setup.md b/.claude/skills/sonarcloud-analysis/references/sonarcloud-setup.md new file mode 100644 index 000000000..ef888a696 --- /dev/null +++ b/.claude/skills/sonarcloud-analysis/references/sonarcloud-setup.md @@ -0,0 +1,85 @@ +# SonarCloud 초기 설정 가이드 + +SonarCloud는 자체 호스팅이 아닌 클라우드 서비스(sonarcloud.io)이므로 별도 서버 설치가 필요 없다. + +## 1. SonarCloud 계정 및 프로젝트 설정 + +1. [sonarcloud.io](https://sonarcloud.io)에서 GitHub 계정으로 로그인 +2. 조직(Organization) 생성 또는 기존 GitHub 조직 연결 +3. 새 프로젝트 추가 → KOIN_WEB_RECODE 저장소 선택 +4. 프로젝트 키 확인 (기본값: `BCSDLab_KOIN_WEB_RECODE` 형태) + +## 2. sonar-project.properties 생성 + +프로젝트 루트에 다음 파일을 생성한다: + +```properties +# sonar-project.properties +sonar.projectKey=BCSDLab_KOIN_WEB_RECODE +sonar.organization=bcsdlab + +sonar.sources=src +sonar.exclusions=src/generated/**,**/*.test.ts,**/*.test.tsx,**/__tests__/** + +sonar.typescript.tsconfigPath=tsconfig.json +sonar.javascript.lcov.reportPaths=coverage/lcov.info +``` + +`sonar.projectKey`와 `sonar.organization`은 SonarCloud 프로젝트 설정 페이지에서 확인한다. + +## 3. GitHub Actions에 SonarCloud 추가 + +별도 워크플로우 파일을 생성한다: + +```yaml +# .github/workflows/sonarcloud.yml +name: SonarCloud Analysis + +on: + pull_request: + branches: [develop, main] + push: + branches: [develop] + +jobs: + sonarcloud: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20.11.1' + + - name: Enable Corepack + run: corepack enable + + - name: Install dependencies + run: yarn install + + - name: SonarCloud Scan + uses: SonarSource/sonarcloud-github-action@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} +``` + +`SONAR_HOST_URL`은 SonarCloud 사용 시 불필요하다. + +## 4. GitHub Secrets 설정 + +GitHub 저장소 Settings > Secrets and variables > Actions에 추가: +- `SONAR_TOKEN`: SonarCloud Personal Access Token + - 발급 위치: sonarcloud.io > My Account > Security > Generate Tokens + +## 5. 로컬 환경변수 설정 + +```bash +# ~/.zshrc 또는 .env.local +export SONAR_ORGANIZATION=bcsdlab +export SONAR_PROJECT_KEY=BCSDLab_KOIN_WEB_RECODE +export SONAR_TOKEN=your-personal-access-token +``` diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml new file mode 100644 index 000000000..bab924c4b --- /dev/null +++ b/.github/workflows/sonarcloud.yml @@ -0,0 +1,29 @@ +name: SonarCloud Analysis + +on: + push: + branches: + - main + - develop + pull_request: + branches: + - main + - develop + +jobs: + sonarcloud: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-node@v4 + with: + node-version: 20 + - name: Install package + run: yarn + - name: SonarCloud Scan + uses: SonarSource/sonarcloud-github-action@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.gitignore b/.gitignore index c18065846..bea56b9b3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,9 @@ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. +# AI pipeline workspace (중간 산출물, 버전 관리 불필요) +_workspace/ +_workspace_prev/ + # .gitignore .yarn/* .yarn/cache/fsevents* diff --git a/.pnp.cjs b/.pnp.cjs old mode 100755 new mode 100644 index 2d8883538..ae7e0c58a --- a/.pnp.cjs +++ b/.pnp.cjs @@ -45,10 +45,10 @@ const RAW_RUNTIME_STATE = ["@types/jest", "npm:27.5.2"],\ ["@types/navermaps", "npm:3.7.4"],\ ["@types/node", "npm:16.18.126"],\ - ["@types/react", "npm:19.2.10"],\ + ["@types/react", "npm:19.2.14"],\ ["@types/react-dom", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:19.2.3"],\ ["@types/react-window", "npm:1.8.8"],\ - ["axios", "npm:0.31.0"],\ + ["axios", "npm:0.31.1"],\ ["dayjs", "npm:1.11.12"],\ ["dotenv", "npm:17.2.3"],\ ["embla-carousel-autoplay", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:8.0.4"],\ @@ -70,11 +70,11 @@ const RAW_RUNTIME_STATE = ["jest", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:29.7.0"],\ ["koin_web_recode", "workspace:."],\ ["lottie-react", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:2.4.1"],\ - ["next", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:15.5.15"],\ - ["postcss", "npm:8.4.47"],\ + ["next", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:15.5.16"],\ + ["postcss", "npm:8.5.14"],\ ["prettier", "npm:3.6.2"],\ - ["react", "npm:19.2.5"],\ - ["react-dom", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:19.2.5"],\ + ["react", "npm:19.2.6"],\ + ["react-dom", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:19.2.6"],\ ["react-hook-form", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:7.60.0"],\ ["react-quill", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:2.0.0"],\ ["react-quill-new", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:3.6.0"],\ @@ -3403,11 +3403,10 @@ const RAW_RUNTIME_STATE = }]\ ]],\ ["@babel/runtime", [\ - ["npm:7.23.9", {\ - "packageLocation": "./.yarn/cache/@babel-runtime-npm-7.23.9-3b96e23cc2-9a520fe1bf.zip/node_modules/@babel/runtime/",\ + ["npm:7.29.2", {\ + "packageLocation": "./.yarn/cache/@babel-runtime-npm-7.29.2-b49cad1c67-f55ba4052a.zip/node_modules/@babel/runtime/",\ "packageDependencies": [\ - ["@babel/runtime", "npm:7.23.9"],\ - ["regenerator-runtime", "npm:0.14.1"]\ + ["@babel/runtime", "npm:7.29.2"]\ ],\ "linkType": "HARD"\ }]\ @@ -3630,7 +3629,7 @@ const RAW_RUNTIME_STATE = "packageDependencies": [\ ["@bcsdlab/koin", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:0.0.15"],\ ["@types/axios", null],\ - ["axios", "npm:0.31.0"]\ + ["axios", "npm:0.31.1"]\ ],\ "packagePeers": [\ "@types/axios",\ @@ -4061,7 +4060,7 @@ const RAW_RUNTIME_STATE = ["@eslint/config-array", "npm:0.21.1"],\ ["@eslint/object-schema", "npm:2.1.7"],\ ["debug", "virtual:428f325a939c2653ad822eb3d75efb02ac311523dd0d4f9645afc39ea00bd86eceac35a9d59c9b6977d76b670a4ef0ae057ea572338a44729aa592711a8c05a3#npm:4.3.4"],\ - ["minimatch", "npm:3.1.2"]\ + ["minimatch", "npm:3.1.5"]\ ],\ "linkType": "HARD"\ }]\ @@ -4098,7 +4097,7 @@ const RAW_RUNTIME_STATE = ["ignore", "npm:5.3.1"],\ ["import-fresh", "npm:3.3.0"],\ ["js-yaml", "npm:4.1.0"],\ - ["minimatch", "npm:3.1.2"],\ + ["minimatch", "npm:3.1.5"],\ ["strip-json-comments", "npm:3.1.1"]\ ],\ "linkType": "HARD"\ @@ -4437,7 +4436,7 @@ const RAW_RUNTIME_STATE = ["camelcase", "npm:5.3.1"],\ ["find-up", "npm:4.1.0"],\ ["get-package-type", "npm:0.1.0"],\ - ["js-yaml", "npm:3.14.1"],\ + ["js-yaml", "npm:3.14.2"],\ ["resolve-from", "npm:5.0.0"]\ ],\ "linkType": "HARD"\ @@ -4837,10 +4836,10 @@ const RAW_RUNTIME_STATE = }]\ ]],\ ["@next/env", [\ - ["npm:15.5.15", {\ - "packageLocation": "./.yarn/cache/@next-env-npm-15.5.15-a6a238e240-5633403906.zip/node_modules/@next/env/",\ + ["npm:15.5.16", {\ + "packageLocation": "./.yarn/cache/@next-env-npm-15.5.16-451e0aa651-4ba5975b7b.zip/node_modules/@next/env/",\ "packageDependencies": [\ - ["@next/env", "npm:15.5.15"]\ + ["@next/env", "npm:15.5.16"]\ ],\ "linkType": "HARD"\ }]\ @@ -4856,73 +4855,73 @@ const RAW_RUNTIME_STATE = }]\ ]],\ ["@next/swc-darwin-arm64", [\ - ["npm:15.5.15", {\ - "packageLocation": "./.yarn/unplugged/@next-swc-darwin-arm64-npm-15.5.15-745fcf051d/node_modules/@next/swc-darwin-arm64/",\ + ["npm:15.5.16", {\ + "packageLocation": "./.yarn/unplugged/@next-swc-darwin-arm64-npm-15.5.16-3ce66e459a/node_modules/@next/swc-darwin-arm64/",\ "packageDependencies": [\ - ["@next/swc-darwin-arm64", "npm:15.5.15"]\ + ["@next/swc-darwin-arm64", "npm:15.5.16"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["@next/swc-darwin-x64", [\ - ["npm:15.5.15", {\ - "packageLocation": "./.yarn/unplugged/@next-swc-darwin-x64-npm-15.5.15-0a9f5d9405/node_modules/@next/swc-darwin-x64/",\ + ["npm:15.5.16", {\ + "packageLocation": "./.yarn/unplugged/@next-swc-darwin-x64-npm-15.5.16-feac411ff1/node_modules/@next/swc-darwin-x64/",\ "packageDependencies": [\ - ["@next/swc-darwin-x64", "npm:15.5.15"]\ + ["@next/swc-darwin-x64", "npm:15.5.16"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["@next/swc-linux-arm64-gnu", [\ - ["npm:15.5.15", {\ - "packageLocation": "./.yarn/unplugged/@next-swc-linux-arm64-gnu-npm-15.5.15-acc4f47d1a/node_modules/@next/swc-linux-arm64-gnu/",\ + ["npm:15.5.16", {\ + "packageLocation": "./.yarn/unplugged/@next-swc-linux-arm64-gnu-npm-15.5.16-f580e21375/node_modules/@next/swc-linux-arm64-gnu/",\ "packageDependencies": [\ - ["@next/swc-linux-arm64-gnu", "npm:15.5.15"]\ + ["@next/swc-linux-arm64-gnu", "npm:15.5.16"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["@next/swc-linux-arm64-musl", [\ - ["npm:15.5.15", {\ - "packageLocation": "./.yarn/unplugged/@next-swc-linux-arm64-musl-npm-15.5.15-40af07d3ca/node_modules/@next/swc-linux-arm64-musl/",\ + ["npm:15.5.16", {\ + "packageLocation": "./.yarn/unplugged/@next-swc-linux-arm64-musl-npm-15.5.16-d922be8959/node_modules/@next/swc-linux-arm64-musl/",\ "packageDependencies": [\ - ["@next/swc-linux-arm64-musl", "npm:15.5.15"]\ + ["@next/swc-linux-arm64-musl", "npm:15.5.16"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["@next/swc-linux-x64-gnu", [\ - ["npm:15.5.15", {\ - "packageLocation": "./.yarn/unplugged/@next-swc-linux-x64-gnu-npm-15.5.15-39e2669d0f/node_modules/@next/swc-linux-x64-gnu/",\ + ["npm:15.5.16", {\ + "packageLocation": "./.yarn/unplugged/@next-swc-linux-x64-gnu-npm-15.5.16-f7eed85bcd/node_modules/@next/swc-linux-x64-gnu/",\ "packageDependencies": [\ - ["@next/swc-linux-x64-gnu", "npm:15.5.15"]\ + ["@next/swc-linux-x64-gnu", "npm:15.5.16"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["@next/swc-linux-x64-musl", [\ - ["npm:15.5.15", {\ - "packageLocation": "./.yarn/unplugged/@next-swc-linux-x64-musl-npm-15.5.15-3bbfad9ad0/node_modules/@next/swc-linux-x64-musl/",\ + ["npm:15.5.16", {\ + "packageLocation": "./.yarn/unplugged/@next-swc-linux-x64-musl-npm-15.5.16-61ca6ad652/node_modules/@next/swc-linux-x64-musl/",\ "packageDependencies": [\ - ["@next/swc-linux-x64-musl", "npm:15.5.15"]\ + ["@next/swc-linux-x64-musl", "npm:15.5.16"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["@next/swc-win32-arm64-msvc", [\ - ["npm:15.5.15", {\ - "packageLocation": "./.yarn/unplugged/@next-swc-win32-arm64-msvc-npm-15.5.15-49e9a2b3ee/node_modules/@next/swc-win32-arm64-msvc/",\ + ["npm:15.5.16", {\ + "packageLocation": "./.yarn/unplugged/@next-swc-win32-arm64-msvc-npm-15.5.16-e11efd2e14/node_modules/@next/swc-win32-arm64-msvc/",\ "packageDependencies": [\ - ["@next/swc-win32-arm64-msvc", "npm:15.5.15"]\ + ["@next/swc-win32-arm64-msvc", "npm:15.5.16"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["@next/swc-win32-x64-msvc", [\ - ["npm:15.5.15", {\ - "packageLocation": "./.yarn/unplugged/@next-swc-win32-x64-msvc-npm-15.5.15-06aae24a5f/node_modules/@next/swc-win32-x64-msvc/",\ + ["npm:15.5.16", {\ + "packageLocation": "./.yarn/unplugged/@next-swc-win32-x64-msvc-npm-15.5.16-4b3d698c3f/node_modules/@next/swc-win32-x64-msvc/",\ "packageDependencies": [\ - ["@next/swc-win32-x64-msvc", "npm:15.5.15"]\ + ["@next/swc-win32-x64-msvc", "npm:15.5.16"]\ ],\ "linkType": "HARD"\ }]\ @@ -4940,9 +4939,9 @@ const RAW_RUNTIME_STATE = "packageDependencies": [\ ["@next/third-parties", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:15.5.2"],\ ["@types/next", null],\ - ["@types/react", "npm:19.2.10"],\ - ["next", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:15.5.15"],\ - ["react", "npm:19.2.5"],\ + ["@types/react", "npm:19.2.14"],\ + ["next", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:15.5.16"],\ + ["react", "npm:19.2.6"],\ ["third-party-capital", "npm:1.0.20"]\ ],\ "packagePeers": [\ @@ -6438,7 +6437,7 @@ const RAW_RUNTIME_STATE = ["@sentry/vercel-edge", "npm:10.43.0"],\ ["@sentry/webpack-plugin", "virtual:b61873f9545530461749d78f9bde76fc9a6b6dc92c78702c77f1a1597fec363ac41367267acf9ec6c5179e1f1550f9a2d091fe865cb4d3c0e9f1536897e31401#npm:5.1.1"],\ ["@types/next", null],\ - ["next", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:15.5.15"],\ + ["next", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:15.5.16"],\ ["rollup", "npm:4.59.0"],\ ["stacktrace-parser", "npm:0.1.11"]\ ],\ @@ -6973,7 +6972,7 @@ const RAW_RUNTIME_STATE = ["@types/svgr__core", null],\ ["cosmiconfig", "virtual:77cc138b8f03e76bc81b53d7c536b91810383e8b8e9f294aac9f969daaf6fd5a72e29c2099e3025b537aac701c45d44c7e6af1647a2799c9f61d4f4f9312b7a3#npm:8.3.6"],\ ["deepmerge", "npm:4.3.1"],\ - ["svgo", "npm:3.3.2"]\ + ["svgo", "npm:3.3.3"]\ ],\ "packagePeers": [\ "@svgr/core",\ @@ -7031,8 +7030,8 @@ const RAW_RUNTIME_STATE = "packageDependencies": [\ ["@tanstack/query-core", "npm:5.90.20"],\ ["@tanstack/react-query", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:5.90.21"],\ - ["@types/react", "npm:19.2.10"],\ - ["react", "npm:19.2.5"]\ + ["@types/react", "npm:19.2.14"],\ + ["react", "npm:19.2.6"]\ ],\ "packagePeers": [\ "@types/react",\ @@ -7046,7 +7045,7 @@ const RAW_RUNTIME_STATE = "packageLocation": "./.yarn/cache/@testing-library-dom-npm-8.20.1-453580d160-6c7a92fcc8.zip/node_modules/@testing-library/dom/",\ "packageDependencies": [\ ["@babel/code-frame", "npm:7.23.5"],\ - ["@babel/runtime", "npm:7.23.9"],\ + ["@babel/runtime", "npm:7.29.2"],\ ["@testing-library/dom", "npm:8.20.1"],\ ["@types/aria-query", "npm:5.0.4"],\ ["aria-query", "npm:5.1.3"],\ @@ -7063,14 +7062,14 @@ const RAW_RUNTIME_STATE = "packageLocation": "./.yarn/cache/@testing-library-jest-dom-npm-5.17.0-a702605ee4-5a75f2094f.zip/node_modules/@testing-library/jest-dom/",\ "packageDependencies": [\ ["@adobe/css-tools", "npm:4.3.3"],\ - ["@babel/runtime", "npm:7.23.9"],\ + ["@babel/runtime", "npm:7.29.2"],\ ["@testing-library/jest-dom", "npm:5.17.0"],\ ["@types/testing-library__jest-dom", "npm:5.14.9"],\ ["aria-query", "npm:5.3.0"],\ ["chalk", "npm:3.0.0"],\ ["css.escape", "npm:1.5.1"],\ ["dom-accessibility-api", "npm:0.5.16"],\ - ["lodash", "npm:4.17.21"],\ + ["lodash", "npm:4.18.1"],\ ["redent", "npm:3.0.0"]\ ],\ "linkType": "HARD"\ @@ -7087,13 +7086,13 @@ const RAW_RUNTIME_STATE = ["virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:13.4.0", {\ "packageLocation": "./.yarn/__virtual__/@testing-library-react-virtual-080cf6b7af/0/cache/@testing-library-react-npm-13.4.0-eaa652c0f5-788249aad2.zip/node_modules/@testing-library/react/",\ "packageDependencies": [\ - ["@babel/runtime", "npm:7.23.9"],\ + ["@babel/runtime", "npm:7.29.2"],\ ["@testing-library/dom", "npm:8.20.1"],\ ["@testing-library/react", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:13.4.0"],\ - ["@types/react", "npm:19.2.10"],\ + ["@types/react", "npm:19.2.14"],\ ["@types/react-dom", "npm:18.2.19"],\ - ["react", "npm:19.2.5"],\ - ["react-dom", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:19.2.5"]\ + ["react", "npm:19.2.6"],\ + ["react-dom", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:19.2.6"]\ ],\ "packagePeers": [\ "@types/react",\ @@ -7114,7 +7113,7 @@ const RAW_RUNTIME_STATE = ["virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:13.5.0", {\ "packageLocation": "./.yarn/__virtual__/@testing-library-user-event-virtual-a955a8f5ed/0/cache/@testing-library-user-event-npm-13.5.0-1ff89b703a-e2bdf2b237.zip/node_modules/@testing-library/user-event/",\ "packageDependencies": [\ - ["@babel/runtime", "npm:7.23.9"],\ + ["@babel/runtime", "npm:7.29.2"],\ ["@testing-library/dom", null],\ ["@testing-library/user-event", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:13.5.0"],\ ["@types/testing-library__dom", null]\ @@ -7126,15 +7125,6 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ - ["@trysound/sax", [\ - ["npm:0.2.0", {\ - "packageLocation": "./.yarn/cache/@trysound-sax-npm-0.2.0-9f763d0295-7379713eca.zip/node_modules/@trysound/sax/",\ - "packageDependencies": [\ - ["@trysound/sax", "npm:0.2.0"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["@tybys/wasm-util", [\ ["npm:0.10.1", {\ "packageLocation": "./.yarn/cache/@tybys-wasm-util-npm-0.10.1-607c8a7e5c-7fe0d23939.zip/node_modules/@tybys/wasm-util/",\ @@ -7465,10 +7455,10 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "HARD"\ }],\ - ["npm:19.2.10", {\ - "packageLocation": "./.yarn/cache/@types-react-npm-19.2.10-92289ba21f-0e34a0e42d.zip/node_modules/@types/react/",\ + ["npm:19.2.14", {\ + "packageLocation": "./.yarn/cache/@types-react-npm-19.2.14-072ed0943f-fbff239089.zip/node_modules/@types/react/",\ "packageDependencies": [\ - ["@types/react", "npm:19.2.10"],\ + ["@types/react", "npm:19.2.14"],\ ["csstype", "npm:3.2.3"]\ ],\ "linkType": "HARD"\ @@ -7493,7 +7483,7 @@ const RAW_RUNTIME_STATE = ["virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:19.2.3", {\ "packageLocation": "./.yarn/__virtual__/@types-react-dom-virtual-66384c709c/0/cache/@types-react-dom-npm-19.2.3-1b243fa1cb-616c4a8aee.zip/node_modules/@types/react-dom/",\ "packageDependencies": [\ - ["@types/react", "npm:19.2.10"],\ + ["@types/react", "npm:19.2.14"],\ ["@types/react-dom", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:19.2.3"]\ ],\ "packagePeers": [\ @@ -8161,7 +8151,7 @@ const RAW_RUNTIME_STATE = "packageDependencies": [\ ["anymatch", "npm:3.1.3"],\ ["normalize-path", "npm:3.0.0"],\ - ["picomatch", "npm:2.3.1"]\ + ["picomatch", "npm:2.3.2"]\ ],\ "linkType": "HARD"\ }]\ @@ -8471,11 +8461,11 @@ const RAW_RUNTIME_STATE = }]\ ]],\ ["axios", [\ - ["npm:0.31.0", {\ - "packageLocation": "./.yarn/cache/axios-npm-0.31.0-c3cc4d3e42-80536a915c.zip/node_modules/axios/",\ + ["npm:0.31.1", {\ + "packageLocation": "./.yarn/cache/axios-npm-0.31.1-0574a0de7d-097dffdc0a.zip/node_modules/axios/",\ "packageDependencies": [\ - ["axios", "npm:0.31.0"],\ - ["follow-redirects", "virtual:c3cc4d3e42e0cbc24baf969e9e9c4d3a62a5b9e6de095ef349c8ef855208fd6fff9a9d64360963b98fa8437dabdf13fd910e8e20dac0ba935d3dec8e4d29ac0a#npm:1.16.0"],\ + ["axios", "npm:0.31.1"],\ + ["follow-redirects", "virtual:0574a0de7d342461fa146d11ae87724ae03b292c6cf3a99ab5c90c004e7f3edcc2c36de3142ff4f8dd372c89635bfe914b3cf8526f198e64a9fb81d909d1c12d#npm:1.16.0"],\ ["form-data", "npm:4.0.5"],\ ["proxy-from-env", "npm:1.1.0"]\ ],\ @@ -8757,11 +8747,11 @@ const RAW_RUNTIME_STATE = }]\ ]],\ ["brace-expansion", [\ - ["npm:1.1.11", {\ - "packageLocation": "./.yarn/cache/brace-expansion-npm-1.1.11-fb95eb05ad-faf34a7bb0.zip/node_modules/brace-expansion/",\ + ["npm:1.1.14", {\ + "packageLocation": "./.yarn/cache/brace-expansion-npm-1.1.14-a997f4f4e7-2de747a589.zip/node_modules/brace-expansion/",\ "packageDependencies": [\ ["balanced-match", "npm:1.0.2"],\ - ["brace-expansion", "npm:1.1.11"],\ + ["brace-expansion", "npm:1.1.14"],\ ["concat-map", "npm:0.0.1"]\ ],\ "linkType": "HARD"\ @@ -8784,11 +8774,11 @@ const RAW_RUNTIME_STATE = }]\ ]],\ ["braces", [\ - ["npm:3.0.2", {\ - "packageLocation": "./.yarn/cache/braces-npm-3.0.2-782240b28a-966b1fb48d.zip/node_modules/braces/",\ + ["npm:3.0.3", {\ + "packageLocation": "./.yarn/cache/braces-npm-3.0.3-582c14023c-fad11a0d46.zip/node_modules/braces/",\ "packageDependencies": [\ - ["braces", "npm:3.0.2"],\ - ["fill-range", "npm:7.0.1"]\ + ["braces", "npm:3.0.3"],\ + ["fill-range", "npm:7.1.1"]\ ],\ "linkType": "HARD"\ }]\ @@ -8851,7 +8841,7 @@ const RAW_RUNTIME_STATE = ["minipass-pipeline", "npm:1.2.4"],\ ["p-map", "npm:4.0.0"],\ ["ssri", "npm:10.0.5"],\ - ["tar", "npm:6.2.0"],\ + ["tar", "npm:6.2.1"],\ ["unique-filename", "npm:3.0.0"]\ ],\ "linkType": "HARD"\ @@ -9007,7 +8997,7 @@ const RAW_RUNTIME_STATE = "packageLocation": "./.yarn/cache/chokidar-npm-3.6.0-3c413a828f-c327fb0770.zip/node_modules/chokidar/",\ "packageDependencies": [\ ["anymatch", "npm:3.1.3"],\ - ["braces", "npm:3.0.2"],\ + ["braces", "npm:3.0.3"],\ ["chokidar", "npm:3.6.0"],\ ["fsevents", "patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1"],\ ["glob-parent", "npm:5.1.2"],\ @@ -9961,11 +9951,11 @@ const RAW_RUNTIME_STATE = ["virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:8.0.4", {\ "packageLocation": "./.yarn/__virtual__/embla-carousel-react-virtual-60bfd0584c/0/cache/embla-carousel-react-npm-8.0.4-5f1cf2e339-db312b00c3.zip/node_modules/embla-carousel-react/",\ "packageDependencies": [\ - ["@types/react", "npm:19.2.10"],\ + ["@types/react", "npm:19.2.14"],\ ["embla-carousel", "npm:8.0.4"],\ ["embla-carousel-react", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:8.0.4"],\ ["embla-carousel-reactive-utils", "virtual:60bfd0584c1b078dcddec94ff9abbf93ec558d778bababdeff248a8dbfa8ff1cbb64fd9d392d6afc96b07504b72cb77258271a8c052f017d0731160303d4aeea#npm:8.0.4"],\ - ["react", "npm:19.2.5"]\ + ["react", "npm:19.2.6"]\ ],\ "packagePeers": [\ "@types/react",\ @@ -10423,7 +10413,7 @@ const RAW_RUNTIME_STATE = ["jiti", null],\ ["json-stable-stringify-without-jsonify", "npm:1.0.1"],\ ["lodash.merge", "npm:4.6.2"],\ - ["minimatch", "npm:3.1.2"],\ + ["minimatch", "npm:3.1.5"],\ ["natural-compare", "npm:1.4.0"],\ ["optionator", "npm:0.9.3"]\ ],\ @@ -10596,7 +10586,7 @@ const RAW_RUNTIME_STATE = ["hasown", "npm:2.0.2"],\ ["is-core-module", "npm:2.16.1"],\ ["is-glob", "npm:4.0.3"],\ - ["minimatch", "npm:3.1.2"],\ + ["minimatch", "npm:3.1.5"],\ ["object.fromentries", "npm:2.0.8"],\ ["object.groupby", "npm:1.0.3"],\ ["object.values", "npm:1.2.1"],\ @@ -10638,7 +10628,7 @@ const RAW_RUNTIME_STATE = ["hasown", "npm:2.0.2"],\ ["jsx-ast-utils", "npm:3.3.5"],\ ["language-tags", "npm:1.0.9"],\ - ["minimatch", "npm:3.1.2"],\ + ["minimatch", "npm:3.1.5"],\ ["object.fromentries", "npm:2.0.8"],\ ["safe-regex-test", "npm:1.1.0"],\ ["string.prototype.includes", "npm:2.0.1"]\ @@ -10705,7 +10695,7 @@ const RAW_RUNTIME_STATE = ["estraverse", "npm:5.3.0"],\ ["hasown", "npm:2.0.2"],\ ["jsx-ast-utils", "npm:3.3.5"],\ - ["minimatch", "npm:3.1.2"],\ + ["minimatch", "npm:3.1.5"],\ ["object.entries", "npm:1.1.9"],\ ["object.fromentries", "npm:2.0.8"],\ ["object.values", "npm:1.2.1"],\ @@ -11067,10 +11057,10 @@ const RAW_RUNTIME_STATE = }]\ ]],\ ["fill-range", [\ - ["npm:7.0.1", {\ - "packageLocation": "./.yarn/cache/fill-range-npm-7.0.1-b8b1817caa-e260f7592f.zip/node_modules/fill-range/",\ + ["npm:7.1.1", {\ + "packageLocation": "./.yarn/cache/fill-range-npm-7.1.1-bf491486db-a7095cb39e.zip/node_modules/fill-range/",\ "packageDependencies": [\ - ["fill-range", "npm:7.0.1"],\ + ["fill-range", "npm:7.1.1"],\ ["to-regex-range", "npm:5.0.1"]\ ],\ "linkType": "HARD"\ @@ -11101,7 +11091,7 @@ const RAW_RUNTIME_STATE = "packageLocation": "./.yarn/cache/flat-cache-npm-3.2.0-9a887f084e-02381c6ece.zip/node_modules/flat-cache/",\ "packageDependencies": [\ ["flat-cache", "npm:3.2.0"],\ - ["flatted", "npm:3.2.9"],\ + ["flatted", "npm:3.4.2"],\ ["keyv", "npm:4.5.4"],\ ["rimraf", "npm:3.0.2"]\ ],\ @@ -11111,7 +11101,7 @@ const RAW_RUNTIME_STATE = "packageLocation": "./.yarn/cache/flat-cache-npm-4.0.0-af74b9d632-344c60d397.zip/node_modules/flat-cache/",\ "packageDependencies": [\ ["flat-cache", "npm:4.0.0"],\ - ["flatted", "npm:3.2.9"],\ + ["flatted", "npm:3.4.2"],\ ["keyv", "npm:4.5.4"],\ ["rimraf", "npm:5.0.5"]\ ],\ @@ -11119,10 +11109,10 @@ const RAW_RUNTIME_STATE = }]\ ]],\ ["flatted", [\ - ["npm:3.2.9", {\ - "packageLocation": "./.yarn/cache/flatted-npm-3.2.9-0462256d3c-dc2b89e46a.zip/node_modules/flatted/",\ + ["npm:3.4.2", {\ + "packageLocation": "./.yarn/cache/flatted-npm-3.4.2-e32280259b-a9e78fe5c2.zip/node_modules/flatted/",\ "packageDependencies": [\ - ["flatted", "npm:3.2.9"]\ + ["flatted", "npm:3.4.2"]\ ],\ "linkType": "HARD"\ }]\ @@ -11135,12 +11125,12 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "SOFT"\ }],\ - ["virtual:c3cc4d3e42e0cbc24baf969e9e9c4d3a62a5b9e6de095ef349c8ef855208fd6fff9a9d64360963b98fa8437dabdf13fd910e8e20dac0ba935d3dec8e4d29ac0a#npm:1.16.0", {\ - "packageLocation": "./.yarn/__virtual__/follow-redirects-virtual-408cb32876/0/cache/follow-redirects-npm-1.16.0-816e4f62d9-3fbe3d80b3.zip/node_modules/follow-redirects/",\ + ["virtual:0574a0de7d342461fa146d11ae87724ae03b292c6cf3a99ab5c90c004e7f3edcc2c36de3142ff4f8dd372c89635bfe914b3cf8526f198e64a9fb81d909d1c12d#npm:1.16.0", {\ + "packageLocation": "./.yarn/__virtual__/follow-redirects-virtual-575fb99746/0/cache/follow-redirects-npm-1.16.0-816e4f62d9-3fbe3d80b3.zip/node_modules/follow-redirects/",\ "packageDependencies": [\ ["@types/debug", null],\ ["debug", null],\ - ["follow-redirects", "virtual:c3cc4d3e42e0cbc24baf969e9e9c4d3a62a5b9e6de095ef349c8ef855208fd6fff9a9d64360963b98fa8437dabdf13fd910e8e20dac0ba935d3dec8e4d29ac0a#npm:1.16.0"]\ + ["follow-redirects", "virtual:0574a0de7d342461fa146d11ae87724ae03b292c6cf3a99ab5c90c004e7f3edcc2c36de3142ff4f8dd372c89635bfe914b3cf8526f198e64a9fb81d909d1c12d#npm:1.16.0"]\ ],\ "packagePeers": [\ "@types/debug",\ @@ -11214,13 +11204,13 @@ const RAW_RUNTIME_STATE = "packageDependencies": [\ ["@emotion/is-prop-valid", null],\ ["@types/emotion__is-prop-valid", null],\ - ["@types/react", "npm:19.2.10"],\ + ["@types/react", "npm:19.2.14"],\ ["@types/react-dom", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:19.2.3"],\ ["framer-motion", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:12.4.4"],\ ["motion-dom", "npm:12.4.4"],\ ["motion-utils", "npm:12.0.0"],\ - ["react", "npm:19.2.5"],\ - ["react-dom", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:19.2.5"],\ + ["react", "npm:19.2.6"],\ + ["react-dom", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:19.2.6"],\ ["tslib", "npm:2.8.1"]\ ],\ "packagePeers": [\ @@ -11452,7 +11442,7 @@ const RAW_RUNTIME_STATE = ["foreground-child", "npm:3.1.1"],\ ["glob", "npm:10.3.10"],\ ["jackspeak", "npm:2.3.6"],\ - ["minimatch", "npm:9.0.3"],\ + ["minimatch", "npm:9.0.5"],\ ["minipass", "npm:7.0.4"],\ ["path-scurry", "npm:1.10.1"]\ ],\ @@ -11475,7 +11465,7 @@ const RAW_RUNTIME_STATE = ["glob", "npm:7.2.3"],\ ["inflight", "npm:1.0.6"],\ ["inherits", "npm:2.0.4"],\ - ["minimatch", "npm:3.1.2"],\ + ["minimatch", "npm:3.1.5"],\ ["once", "npm:1.4.0"],\ ["path-is-absolute", "npm:1.0.1"]\ ],\ @@ -11803,11 +11793,11 @@ const RAW_RUNTIME_STATE = ["virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:5.1.10", {\ "packageLocation": "./.yarn/__virtual__/html-react-parser-virtual-6fec58a79b/0/cache/html-react-parser-npm-5.1.10-887458c19c-30a116cd46.zip/node_modules/html-react-parser/",\ "packageDependencies": [\ - ["@types/react", "npm:19.2.10"],\ + ["@types/react", "npm:19.2.14"],\ ["domhandler", "npm:5.0.3"],\ ["html-dom-parser", "npm:5.0.8"],\ ["html-react-parser", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:5.1.10"],\ - ["react", "npm:19.2.5"],\ + ["react", "npm:19.2.6"],\ ["react-property", "npm:2.0.2"],\ ["style-to-js", "npm:1.1.12"]\ ],\ @@ -11936,10 +11926,10 @@ const RAW_RUNTIME_STATE = }]\ ]],\ ["immutable", [\ - ["npm:4.3.5", {\ - "packageLocation": "./.yarn/cache/immutable-npm-4.3.5-5958499808-dbc1b8c808.zip/node_modules/immutable/",\ + ["npm:4.3.8", {\ + "packageLocation": "./.yarn/cache/immutable-npm-4.3.8-9db2d34fb7-27a134cec0.zip/node_modules/immutable/",\ "packageDependencies": [\ - ["immutable", "npm:4.3.5"]\ + ["immutable", "npm:4.3.8"]\ ],\ "linkType": "HARD"\ }]\ @@ -13208,7 +13198,7 @@ const RAW_RUNTIME_STATE = ["ci-info", "npm:3.9.0"],\ ["graceful-fs", "npm:4.2.11"],\ ["jest-util", "npm:29.7.0"],\ - ["picomatch", "npm:2.3.1"]\ + ["picomatch", "npm:2.3.2"]\ ],\ "linkType": "HARD"\ }]\ @@ -13287,12 +13277,12 @@ const RAW_RUNTIME_STATE = }]\ ]],\ ["js-yaml", [\ - ["npm:3.14.1", {\ - "packageLocation": "./.yarn/cache/js-yaml-npm-3.14.1-b968c6095e-9e22d80b4d.zip/node_modules/js-yaml/",\ + ["npm:3.14.2", {\ + "packageLocation": "./.yarn/cache/js-yaml-npm-3.14.2-debd9d20c3-172e0b6007.zip/node_modules/js-yaml/",\ "packageDependencies": [\ ["argparse", "npm:1.0.10"],\ ["esprima", "npm:4.0.1"],\ - ["js-yaml", "npm:3.14.1"]\ + ["js-yaml", "npm:3.14.2"]\ ],\ "linkType": "HARD"\ }],\ @@ -13459,10 +13449,10 @@ const RAW_RUNTIME_STATE = ["@types/jest", "npm:27.5.2"],\ ["@types/navermaps", "npm:3.7.4"],\ ["@types/node", "npm:16.18.126"],\ - ["@types/react", "npm:19.2.10"],\ + ["@types/react", "npm:19.2.14"],\ ["@types/react-dom", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:19.2.3"],\ ["@types/react-window", "npm:1.8.8"],\ - ["axios", "npm:0.31.0"],\ + ["axios", "npm:0.31.1"],\ ["dayjs", "npm:1.11.12"],\ ["dotenv", "npm:17.2.3"],\ ["embla-carousel-autoplay", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:8.0.4"],\ @@ -13484,11 +13474,11 @@ const RAW_RUNTIME_STATE = ["jest", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:29.7.0"],\ ["koin_web_recode", "workspace:."],\ ["lottie-react", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:2.4.1"],\ - ["next", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:15.5.15"],\ - ["postcss", "npm:8.4.47"],\ + ["next", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:15.5.16"],\ + ["postcss", "npm:8.5.14"],\ ["prettier", "npm:3.6.2"],\ - ["react", "npm:19.2.5"],\ - ["react-dom", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:19.2.5"],\ + ["react", "npm:19.2.6"],\ + ["react-dom", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:19.2.6"],\ ["react-hook-form", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:7.60.0"],\ ["react-quill", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:2.0.0"],\ ["react-quill-new", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:3.6.0"],\ @@ -13577,19 +13567,19 @@ const RAW_RUNTIME_STATE = }]\ ]],\ ["lodash", [\ - ["npm:4.17.21", {\ - "packageLocation": "./.yarn/cache/lodash-npm-4.17.21-6382451519-c08619c038.zip/node_modules/lodash/",\ + ["npm:4.18.1", {\ + "packageLocation": "./.yarn/cache/lodash-npm-4.18.1-a64c3070ac-306fea53df.zip/node_modules/lodash/",\ "packageDependencies": [\ - ["lodash", "npm:4.17.21"]\ + ["lodash", "npm:4.18.1"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["lodash-es", [\ - ["npm:4.17.21", {\ - "packageLocation": "./.yarn/cache/lodash-es-npm-4.17.21-b45832dfce-03f39878ea.zip/node_modules/lodash-es/",\ + ["npm:4.18.1", {\ + "packageLocation": "./.yarn/cache/lodash-es-npm-4.18.1-02cf41b912-8bfad225ef.zip/node_modules/lodash-es/",\ "packageDependencies": [\ - ["lodash-es", "npm:4.17.21"]\ + ["lodash-es", "npm:4.18.1"]\ ],\ "linkType": "HARD"\ }]\ @@ -13660,12 +13650,12 @@ const RAW_RUNTIME_STATE = ["virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:2.4.1", {\ "packageLocation": "./.yarn/__virtual__/lottie-react-virtual-8cd5d2f590/0/cache/lottie-react-npm-2.4.1-91badf61e2-d1c54c3d90.zip/node_modules/lottie-react/",\ "packageDependencies": [\ - ["@types/react", "npm:19.2.10"],\ + ["@types/react", "npm:19.2.14"],\ ["@types/react-dom", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:19.2.3"],\ ["lottie-react", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:2.4.1"],\ ["lottie-web", "npm:5.13.0"],\ - ["react", "npm:19.2.5"],\ - ["react-dom", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:19.2.5"]\ + ["react", "npm:19.2.6"],\ + ["react-dom", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:19.2.6"]\ ],\ "packagePeers": [\ "@types/react-dom",\ @@ -13895,9 +13885,9 @@ const RAW_RUNTIME_STATE = ["npm:4.0.5", {\ "packageLocation": "./.yarn/cache/micromatch-npm-4.0.5-cfab5d7669-a749888789.zip/node_modules/micromatch/",\ "packageDependencies": [\ - ["braces", "npm:3.0.2"],\ + ["braces", "npm:3.0.3"],\ ["micromatch", "npm:4.0.5"],\ - ["picomatch", "npm:2.3.1"]\ + ["picomatch", "npm:2.3.2"]\ ],\ "linkType": "HARD"\ }]\ @@ -13948,19 +13938,11 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "HARD"\ }],\ - ["npm:3.1.2", {\ - "packageLocation": "./.yarn/cache/minimatch-npm-3.1.2-9405269906-e0b25b04cd.zip/node_modules/minimatch/",\ + ["npm:3.1.5", {\ + "packageLocation": "./.yarn/cache/minimatch-npm-3.1.5-86958baf50-b11a7ee577.zip/node_modules/minimatch/",\ "packageDependencies": [\ - ["brace-expansion", "npm:1.1.11"],\ - ["minimatch", "npm:3.1.2"]\ - ],\ - "linkType": "HARD"\ - }],\ - ["npm:9.0.3", {\ - "packageLocation": "./.yarn/cache/minimatch-npm-9.0.3-69d7d6fad5-c81b47d281.zip/node_modules/minimatch/",\ - "packageDependencies": [\ - ["brace-expansion", "npm:2.0.1"],\ - ["minimatch", "npm:9.0.3"]\ + ["brace-expansion", "npm:1.1.14"],\ + ["minimatch", "npm:3.1.5"]\ ],\ "linkType": "HARD"\ }],\ @@ -14186,43 +14168,43 @@ const RAW_RUNTIME_STATE = }]\ ]],\ ["next", [\ - ["npm:15.5.15", {\ - "packageLocation": "./.yarn/cache/next-npm-15.5.15-3f1140086c-f15867d9e0.zip/node_modules/next/",\ + ["npm:15.5.16", {\ + "packageLocation": "./.yarn/cache/next-npm-15.5.16-6a72a2b582-6575ffe4b6.zip/node_modules/next/",\ "packageDependencies": [\ - ["next", "npm:15.5.15"]\ + ["next", "npm:15.5.16"]\ ],\ "linkType": "SOFT"\ }],\ - ["virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:15.5.15", {\ - "packageLocation": "./.yarn/__virtual__/next-virtual-53c6a901d0/0/cache/next-npm-15.5.15-3f1140086c-f15867d9e0.zip/node_modules/next/",\ - "packageDependencies": [\ - ["@next/env", "npm:15.5.15"],\ - ["@next/swc-darwin-arm64", "npm:15.5.15"],\ - ["@next/swc-darwin-x64", "npm:15.5.15"],\ - ["@next/swc-linux-arm64-gnu", "npm:15.5.15"],\ - ["@next/swc-linux-arm64-musl", "npm:15.5.15"],\ - ["@next/swc-linux-x64-gnu", "npm:15.5.15"],\ - ["@next/swc-linux-x64-musl", "npm:15.5.15"],\ - ["@next/swc-win32-arm64-msvc", "npm:15.5.15"],\ - ["@next/swc-win32-x64-msvc", "npm:15.5.15"],\ + ["virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:15.5.16", {\ + "packageLocation": "./.yarn/__virtual__/next-virtual-d63923d74b/0/cache/next-npm-15.5.16-6a72a2b582-6575ffe4b6.zip/node_modules/next/",\ + "packageDependencies": [\ + ["@next/env", "npm:15.5.16"],\ + ["@next/swc-darwin-arm64", "npm:15.5.16"],\ + ["@next/swc-darwin-x64", "npm:15.5.16"],\ + ["@next/swc-linux-arm64-gnu", "npm:15.5.16"],\ + ["@next/swc-linux-arm64-musl", "npm:15.5.16"],\ + ["@next/swc-linux-x64-gnu", "npm:15.5.16"],\ + ["@next/swc-linux-x64-musl", "npm:15.5.16"],\ + ["@next/swc-win32-arm64-msvc", "npm:15.5.16"],\ + ["@next/swc-win32-x64-msvc", "npm:15.5.16"],\ ["@opentelemetry/api", null],\ ["@playwright/test", null],\ ["@swc/helpers", "npm:0.5.15"],\ ["@types/babel-plugin-react-compiler", null],\ ["@types/opentelemetry__api", null],\ ["@types/playwright__test", null],\ - ["@types/react", "npm:19.2.10"],\ + ["@types/react", "npm:19.2.14"],\ ["@types/react-dom", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:19.2.3"],\ ["@types/sass", null],\ ["babel-plugin-react-compiler", null],\ ["caniuse-lite", "npm:1.0.30001734"],\ - ["next", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:15.5.15"],\ + ["next", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:15.5.16"],\ ["postcss", "npm:8.4.31"],\ - ["react", "npm:19.2.5"],\ - ["react-dom", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:19.2.5"],\ + ["react", "npm:19.2.6"],\ + ["react-dom", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:19.2.6"],\ ["sass", "npm:1.70.0"],\ ["sharp", "npm:0.34.3"],\ - ["styled-jsx", "virtual:53c6a901d0593fa918daa796c10a3963431dd42f2db704892ff6056104bc0ea9ed8ff66cd98ac51c36d8368a05bd357dfabf0ddb55b01ea2ee6dda96f06b954e#npm:5.1.6"]\ + ["styled-jsx", "virtual:d63923d74be1eb0db06d6252bfdcbe5b974446d09083f65d85707574f1655a77eccd4fe7c5593b6a84267b16b9a23a5eb11e9274b71669a8cc4f764a8f842344#npm:5.1.6"]\ ],\ "packagePeers": [\ "@opentelemetry/api",\ @@ -14288,7 +14270,7 @@ const RAW_RUNTIME_STATE = ["nopt", "npm:7.2.0"],\ ["proc-log", "npm:3.0.0"],\ ["semver", "npm:7.6.0"],\ - ["tar", "npm:6.2.0"],\ + ["tar", "npm:6.2.1"],\ ["which", "npm:4.0.0"]\ ],\ "linkType": "HARD"\ @@ -14783,10 +14765,10 @@ const RAW_RUNTIME_STATE = }]\ ]],\ ["picomatch", [\ - ["npm:2.3.1", {\ - "packageLocation": "./.yarn/cache/picomatch-npm-2.3.1-c782cfd986-60c2595003.zip/node_modules/picomatch/",\ + ["npm:2.3.2", {\ + "packageLocation": "./.yarn/cache/picomatch-npm-2.3.2-4d85543a37-b788ef8148.zip/node_modules/picomatch/",\ "packageDependencies": [\ - ["picomatch", "npm:2.3.1"]\ + ["picomatch", "npm:2.3.2"]\ ],\ "linkType": "HARD"\ }],\ @@ -14856,12 +14838,12 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "HARD"\ }],\ - ["npm:8.4.47", {\ - "packageLocation": "./.yarn/cache/postcss-npm-8.4.47-2f4d4be1fa-f2b50ba9b6.zip/node_modules/postcss/",\ + ["npm:8.5.14", {\ + "packageLocation": "./.yarn/cache/postcss-npm-8.5.14-1cf8d01c78-2e3f4dea69.zip/node_modules/postcss/",\ "packageDependencies": [\ - ["nanoid", "npm:3.3.7"],\ - ["picocolors", "npm:1.1.0"],\ - ["postcss", "npm:8.4.47"],\ + ["nanoid", "npm:3.3.11"],\ + ["picocolors", "npm:1.1.1"],\ + ["postcss", "npm:8.5.14"],\ ["source-map-js", "npm:1.2.1"]\ ],\ "linkType": "HARD"\ @@ -14871,7 +14853,7 @@ const RAW_RUNTIME_STATE = ["npm:3.3.0", {\ "packageLocation": "./.yarn/cache/postcss-bem-linter-npm-3.3.0-94699b7a71-76a7d2f29c.zip/node_modules/postcss-bem-linter/",\ "packageDependencies": [\ - ["minimatch", "npm:3.1.2"],\ + ["minimatch", "npm:3.1.5"],\ ["postcss", "npm:7.0.39"],\ ["postcss-bem-linter", "npm:3.3.0"],\ ["postcss-resolve-nested-selector", "npm:0.1.1"]\ @@ -15183,7 +15165,7 @@ const RAW_RUNTIME_STATE = "packageLocation": "./.yarn/cache/quill-npm-2.0.3-15cb4b4fc0-e4207da225.zip/node_modules/quill/",\ "packageDependencies": [\ ["eventemitter3", "npm:5.0.1"],\ - ["lodash-es", "npm:4.17.21"],\ + ["lodash-es", "npm:4.18.1"],\ ["parchment", "npm:3.0.0"],\ ["quill", "npm:2.0.3"],\ ["quill-delta", "npm:5.1.0"]\ @@ -15214,28 +15196,28 @@ const RAW_RUNTIME_STATE = }]\ ]],\ ["react", [\ - ["npm:19.2.5", {\ - "packageLocation": "./.yarn/cache/react-npm-19.2.5-1e4f979df2-1c3c7ffecb.zip/node_modules/react/",\ + ["npm:19.2.6", {\ + "packageLocation": "./.yarn/cache/react-npm-19.2.6-f326db5d50-205f0db93b.zip/node_modules/react/",\ "packageDependencies": [\ - ["react", "npm:19.2.5"]\ + ["react", "npm:19.2.6"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["react-dom", [\ - ["npm:19.2.5", {\ - "packageLocation": "./.yarn/cache/react-dom-npm-19.2.5-94c3f138c3-ba14b022c7.zip/node_modules/react-dom/",\ + ["npm:19.2.6", {\ + "packageLocation": "./.yarn/cache/react-dom-npm-19.2.6-6f68db79da-695122e37d.zip/node_modules/react-dom/",\ "packageDependencies": [\ - ["react-dom", "npm:19.2.5"]\ + ["react-dom", "npm:19.2.6"]\ ],\ "linkType": "SOFT"\ }],\ - ["virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:19.2.5", {\ - "packageLocation": "./.yarn/__virtual__/react-dom-virtual-c6a8b9fd03/0/cache/react-dom-npm-19.2.5-94c3f138c3-ba14b022c7.zip/node_modules/react-dom/",\ + ["virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:19.2.6", {\ + "packageLocation": "./.yarn/__virtual__/react-dom-virtual-9f9bfcb300/0/cache/react-dom-npm-19.2.6-6f68db79da-695122e37d.zip/node_modules/react-dom/",\ "packageDependencies": [\ - ["@types/react", "npm:19.2.10"],\ - ["react", "npm:19.2.5"],\ - ["react-dom", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:19.2.5"],\ + ["@types/react", "npm:19.2.14"],\ + ["react", "npm:19.2.6"],\ + ["react-dom", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:19.2.6"],\ ["scheduler", "npm:0.27.0"]\ ],\ "packagePeers": [\ @@ -15256,8 +15238,8 @@ const RAW_RUNTIME_STATE = ["virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:7.60.0", {\ "packageLocation": "./.yarn/__virtual__/react-hook-form-virtual-efdffe8144/0/cache/react-hook-form-npm-7.60.0-cd396bdfb0-cc86ef4029.zip/node_modules/react-hook-form/",\ "packageDependencies": [\ - ["@types/react", "npm:19.2.10"],\ - ["react", "npm:19.2.5"],\ + ["@types/react", "npm:19.2.14"],\ + ["react", "npm:19.2.6"],\ ["react-hook-form", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:7.60.0"]\ ],\ "packagePeers": [\ @@ -15311,12 +15293,12 @@ const RAW_RUNTIME_STATE = "packageLocation": "./.yarn/__virtual__/react-quill-virtual-3425ae0628/0/cache/react-quill-npm-2.0.0-f2d141fe1d-e9aa49815b.zip/node_modules/react-quill/",\ "packageDependencies": [\ ["@types/quill", "npm:1.3.10"],\ - ["@types/react", "npm:19.2.10"],\ + ["@types/react", "npm:19.2.14"],\ ["@types/react-dom", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:19.2.3"],\ - ["lodash", "npm:4.17.21"],\ + ["lodash", "npm:4.18.1"],\ ["quill", "npm:1.3.7"],\ - ["react", "npm:19.2.5"],\ - ["react-dom", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:19.2.5"],\ + ["react", "npm:19.2.6"],\ + ["react-dom", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:19.2.6"],\ ["react-quill", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:2.0.0"]\ ],\ "packagePeers": [\ @@ -15340,13 +15322,13 @@ const RAW_RUNTIME_STATE = "packageLocation": "./.yarn/__virtual__/react-quill-new-virtual-786a75c8a7/0/cache/react-quill-new-npm-3.6.0-c5f1c84126-58515b9825.zip/node_modules/react-quill-new/",\ "packageDependencies": [\ ["@types/quill-delta", null],\ - ["@types/react", "npm:19.2.10"],\ + ["@types/react", "npm:19.2.14"],\ ["@types/react-dom", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:19.2.3"],\ - ["lodash-es", "npm:4.17.21"],\ + ["lodash-es", "npm:4.18.1"],\ ["quill", "npm:2.0.3"],\ ["quill-delta", null],\ - ["react", "npm:19.2.5"],\ - ["react-dom", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:19.2.5"],\ + ["react", "npm:19.2.6"],\ + ["react-dom", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:19.2.6"],\ ["react-quill-new", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:3.6.0"]\ ],\ "packagePeers": [\ @@ -15371,8 +15353,8 @@ const RAW_RUNTIME_STATE = ["virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:7.0.2", {\ "packageLocation": "./.yarn/__virtual__/react-swipeable-virtual-52e211cdb8/0/cache/react-swipeable-npm-7.0.2-351f02a9a7-c142190244.zip/node_modules/react-swipeable/",\ "packageDependencies": [\ - ["@types/react", "npm:19.2.10"],\ - ["react", "npm:19.2.5"],\ + ["@types/react", "npm:19.2.14"],\ + ["react", "npm:19.2.6"],\ ["react-swipeable", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:7.0.2"]\ ],\ "packagePeers": [\ @@ -15393,11 +15375,11 @@ const RAW_RUNTIME_STATE = ["virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:11.0.5", {\ "packageLocation": "./.yarn/__virtual__/react-toastify-virtual-c3bd616f1e/0/cache/react-toastify-npm-11.0.5-6e5a4fd835-c27197c2bc.zip/node_modules/react-toastify/",\ "packageDependencies": [\ - ["@types/react", "npm:19.2.10"],\ + ["@types/react", "npm:19.2.14"],\ ["@types/react-dom", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:19.2.3"],\ ["clsx", "npm:2.1.1"],\ - ["react", "npm:19.2.5"],\ - ["react-dom", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:19.2.5"],\ + ["react", "npm:19.2.6"],\ + ["react-dom", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:19.2.6"],\ ["react-toastify", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:11.0.5"]\ ],\ "packagePeers": [\ @@ -15420,12 +15402,12 @@ const RAW_RUNTIME_STATE = ["virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:1.8.10", {\ "packageLocation": "./.yarn/__virtual__/react-window-virtual-f30ea0be84/0/cache/react-window-npm-1.8.10-8350e20b50-6f4a713a20.zip/node_modules/react-window/",\ "packageDependencies": [\ - ["@babel/runtime", "npm:7.23.9"],\ - ["@types/react", "npm:19.2.10"],\ + ["@babel/runtime", "npm:7.29.2"],\ + ["@types/react", "npm:19.2.14"],\ ["@types/react-dom", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:19.2.3"],\ ["memoize-one", "npm:5.2.1"],\ - ["react", "npm:19.2.5"],\ - ["react-dom", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:19.2.5"],\ + ["react", "npm:19.2.6"],\ + ["react-dom", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:19.2.6"],\ ["react-window", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:1.8.10"]\ ],\ "packagePeers": [\ @@ -15466,7 +15448,7 @@ const RAW_RUNTIME_STATE = ["npm:3.6.0", {\ "packageLocation": "./.yarn/cache/readdirp-npm-3.6.0-f950cc74ab-196b30ef6c.zip/node_modules/readdirp/",\ "packageDependencies": [\ - ["picomatch", "npm:2.3.1"],\ + ["picomatch", "npm:2.3.2"],\ ["readdirp", "npm:3.6.0"]\ ],\ "linkType": "HARD"\ @@ -15519,15 +15501,6 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ - ["regenerator-runtime", [\ - ["npm:0.14.1", {\ - "packageLocation": "./.yarn/cache/regenerator-runtime-npm-0.14.1-a6c97c609a-5db3161abb.zip/node_modules/regenerator-runtime/",\ - "packageDependencies": [\ - ["regenerator-runtime", "npm:0.14.1"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["regexp.prototype.flags", [\ ["npm:1.5.1", {\ "packageLocation": "./.yarn/cache/regexp.prototype.flags-npm-1.5.1-b8faeee306-3fa5610b8e.zip/node_modules/regexp.prototype.flags/",\ @@ -15855,13 +15828,22 @@ const RAW_RUNTIME_STATE = "packageLocation": "./.yarn/cache/sass-npm-1.70.0-153257249c-f933545d72.zip/node_modules/sass/",\ "packageDependencies": [\ ["chokidar", "npm:3.6.0"],\ - ["immutable", "npm:4.3.5"],\ + ["immutable", "npm:4.3.8"],\ ["sass", "npm:1.70.0"],\ ["source-map-js", "npm:1.0.2"]\ ],\ "linkType": "HARD"\ }]\ ]],\ + ["sax", [\ + ["npm:1.6.0", {\ + "packageLocation": "./.yarn/cache/sax-npm-1.6.0-39dc3ef158-0909cedcd9.zip/node_modules/sax/",\ + "packageDependencies": [\ + ["sax", "npm:1.6.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["scheduler", [\ ["npm:0.27.0", {\ "packageLocation": "./.yarn/cache/scheduler-npm-0.27.0-772f0dd512-eab3c3a837.zip/node_modules/scheduler/",\ @@ -16585,17 +16567,17 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "SOFT"\ }],\ - ["virtual:53c6a901d0593fa918daa796c10a3963431dd42f2db704892ff6056104bc0ea9ed8ff66cd98ac51c36d8368a05bd357dfabf0ddb55b01ea2ee6dda96f06b954e#npm:5.1.6", {\ - "packageLocation": "./.yarn/__virtual__/styled-jsx-virtual-fa8f217b05/0/cache/styled-jsx-npm-5.1.6-623e2e7d45-ba01200e82.zip/node_modules/styled-jsx/",\ + ["virtual:d63923d74be1eb0db06d6252bfdcbe5b974446d09083f65d85707574f1655a77eccd4fe7c5593b6a84267b16b9a23a5eb11e9274b71669a8cc4f764a8f842344#npm:5.1.6", {\ + "packageLocation": "./.yarn/__virtual__/styled-jsx-virtual-50e2a866a1/0/cache/styled-jsx-npm-5.1.6-623e2e7d45-ba01200e82.zip/node_modules/styled-jsx/",\ "packageDependencies": [\ ["@babel/core", null],\ ["@types/babel-plugin-macros", null],\ ["@types/babel__core", null],\ - ["@types/react", "npm:19.2.10"],\ + ["@types/react", "npm:19.2.14"],\ ["babel-plugin-macros", null],\ ["client-only", "npm:0.0.1"],\ - ["react", "npm:19.2.5"],\ - ["styled-jsx", "virtual:53c6a901d0593fa918daa796c10a3963431dd42f2db704892ff6056104bc0ea9ed8ff66cd98ac51c36d8368a05bd357dfabf0ddb55b01ea2ee6dda96f06b954e#npm:5.1.6"]\ + ["react", "npm:19.2.6"],\ + ["styled-jsx", "virtual:d63923d74be1eb0db06d6252bfdcbe5b974446d09083f65d85707574f1655a77eccd4fe7c5593b6a84267b16b9a23a5eb11e9274b71669a8cc4f764a8f842344#npm:5.1.6"]\ ],\ "packagePeers": [\ "@babel/core",\ @@ -16865,7 +16847,7 @@ const RAW_RUNTIME_STATE = ["npm:2.1.1", {\ "packageLocation": "./.yarn/cache/stylelint-selector-bem-pattern-npm-2.1.1-f7f3e59d73-43e0eacbf1.zip/node_modules/stylelint-selector-bem-pattern/",\ "packageDependencies": [\ - ["lodash", "npm:4.17.21"],\ + ["lodash", "npm:4.18.1"],\ ["postcss", "npm:8.4.35"],\ ["postcss-bem-linter", "npm:3.3.0"],\ ["stylelint", "npm:16.2.1"],\ @@ -16948,17 +16930,17 @@ const RAW_RUNTIME_STATE = }]\ ]],\ ["svgo", [\ - ["npm:3.3.2", {\ - "packageLocation": "./.yarn/cache/svgo-npm-3.3.2-69e1d32944-82fdea9b93.zip/node_modules/svgo/",\ + ["npm:3.3.3", {\ + "packageLocation": "./.yarn/cache/svgo-npm-3.3.3-f4851edd74-f3c1b4d05d.zip/node_modules/svgo/",\ "packageDependencies": [\ - ["@trysound/sax", "npm:0.2.0"],\ ["commander", "npm:7.2.0"],\ ["css-select", "npm:5.2.2"],\ ["css-tree", "npm:2.3.1"],\ ["css-what", "npm:6.2.2"],\ ["csso", "npm:5.0.5"],\ ["picocolors", "npm:1.0.0"],\ - ["svgo", "npm:3.3.2"]\ + ["sax", "npm:1.6.0"],\ + ["svgo", "npm:3.3.3"]\ ],\ "linkType": "HARD"\ }]\ @@ -16988,15 +16970,15 @@ const RAW_RUNTIME_STATE = }]\ ]],\ ["tar", [\ - ["npm:6.2.0", {\ - "packageLocation": "./.yarn/cache/tar-npm-6.2.0-3eb25205a7-2042bbb148.zip/node_modules/tar/",\ + ["npm:6.2.1", {\ + "packageLocation": "./.yarn/cache/tar-npm-6.2.1-237800bb20-bfbfbb2861.zip/node_modules/tar/",\ "packageDependencies": [\ ["chownr", "npm:2.0.0"],\ ["fs-minipass", "npm:2.1.0"],\ ["minipass", "npm:5.0.0"],\ ["minizlib", "npm:2.1.2"],\ ["mkdirp", "npm:1.0.4"],\ - ["tar", "npm:6.2.0"],\ + ["tar", "npm:6.2.1"],\ ["yallist", "npm:4.0.0"]\ ],\ "linkType": "HARD"\ @@ -17008,7 +16990,7 @@ const RAW_RUNTIME_STATE = "packageDependencies": [\ ["@istanbuljs/schema", "npm:0.1.3"],\ ["glob", "npm:7.2.3"],\ - ["minimatch", "npm:3.1.2"],\ + ["minimatch", "npm:3.1.5"],\ ["test-exclude", "npm:6.0.0"]\ ],\ "linkType": "HARD"\ @@ -17535,8 +17517,8 @@ const RAW_RUNTIME_STATE = ["virtual:cd857ad26b8db21c161b6e0ef8419cb17f576cae6fae4b1b5d5b55047147083611649303cacae56e664074d204aad41f1f5522d0749218f8416ccf14aa9ccc48#npm:1.2.0", {\ "packageLocation": "./.yarn/__virtual__/use-sync-external-store-virtual-526cafba24/0/cache/use-sync-external-store-npm-1.2.0-44f75d2564-a676216aff.zip/node_modules/use-sync-external-store/",\ "packageDependencies": [\ - ["@types/react", "npm:19.2.10"],\ - ["react", "npm:19.2.5"],\ + ["@types/react", "npm:19.2.14"],\ + ["react", "npm:19.2.6"],\ ["use-sync-external-store", "virtual:cd857ad26b8db21c161b6e0ef8419cb17f576cae6fae4b1b5d5b55047147083611649303cacae56e664074d204aad41f1f5522d0749218f8416ccf14aa9ccc48#npm:1.2.0"]\ ],\ "packagePeers": [\ @@ -17949,9 +17931,9 @@ const RAW_RUNTIME_STATE = "packageLocation": "./.yarn/__virtual__/zustand-virtual-cd857ad26b/0/cache/zustand-npm-4.5.2-4f034a2f1c-9e9e92ce73.zip/node_modules/zustand/",\ "packageDependencies": [\ ["@types/immer", null],\ - ["@types/react", "npm:19.2.10"],\ + ["@types/react", "npm:19.2.14"],\ ["immer", null],\ - ["react", "npm:19.2.5"],\ + ["react", "npm:19.2.6"],\ ["use-sync-external-store", "virtual:cd857ad26b8db21c161b6e0ef8419cb17f576cae6fae4b1b5d5b55047147083611649303cacae56e664074d204aad41f1f5522d0749218f8416ccf14aa9ccc48#npm:1.2.0"],\ ["zustand", "virtual:921150aa31da2575af7c36f953e9f13b3419705f08359e02e507cdb46eef3a76096cce8027f1cca0709c04e91d009a713934e907c9c1efc1e28e5b528ec25863#npm:4.5.2"]\ ],\ diff --git a/.yarn/cache/@babel-runtime-npm-7.23.9-3b96e23cc2-9a520fe1bf.zip b/.yarn/cache/@babel-runtime-npm-7.23.9-3b96e23cc2-9a520fe1bf.zip deleted file mode 100644 index ef84aa1a7..000000000 Binary files a/.yarn/cache/@babel-runtime-npm-7.23.9-3b96e23cc2-9a520fe1bf.zip and /dev/null differ diff --git a/.yarn/cache/@babel-runtime-npm-7.29.2-b49cad1c67-f55ba4052a.zip b/.yarn/cache/@babel-runtime-npm-7.29.2-b49cad1c67-f55ba4052a.zip new file mode 100644 index 000000000..3fea8ec71 Binary files /dev/null and b/.yarn/cache/@babel-runtime-npm-7.29.2-b49cad1c67-f55ba4052a.zip differ diff --git a/.yarn/cache/@next-env-npm-15.5.15-a6a238e240-5633403906.zip b/.yarn/cache/@next-env-npm-15.5.16-451e0aa651-4ba5975b7b.zip similarity index 71% rename from .yarn/cache/@next-env-npm-15.5.15-a6a238e240-5633403906.zip rename to .yarn/cache/@next-env-npm-15.5.16-451e0aa651-4ba5975b7b.zip index e42365129..40fadb15b 100644 Binary files a/.yarn/cache/@next-env-npm-15.5.15-a6a238e240-5633403906.zip and b/.yarn/cache/@next-env-npm-15.5.16-451e0aa651-4ba5975b7b.zip differ diff --git a/.yarn/cache/@next-swc-darwin-arm64-npm-15.5.15-745fcf051d-10.zip b/.yarn/cache/@next-swc-darwin-arm64-npm-15.5.16-3ce66e459a-10.zip similarity index 77% rename from .yarn/cache/@next-swc-darwin-arm64-npm-15.5.15-745fcf051d-10.zip rename to .yarn/cache/@next-swc-darwin-arm64-npm-15.5.16-3ce66e459a-10.zip index 7011f7ecb..e91f02308 100644 Binary files a/.yarn/cache/@next-swc-darwin-arm64-npm-15.5.15-745fcf051d-10.zip and b/.yarn/cache/@next-swc-darwin-arm64-npm-15.5.16-3ce66e459a-10.zip differ diff --git a/.yarn/cache/@trysound-sax-npm-0.2.0-9f763d0295-7379713eca.zip b/.yarn/cache/@trysound-sax-npm-0.2.0-9f763d0295-7379713eca.zip deleted file mode 100644 index 63571afe2..000000000 Binary files a/.yarn/cache/@trysound-sax-npm-0.2.0-9f763d0295-7379713eca.zip and /dev/null differ diff --git a/.yarn/cache/@types-react-npm-19.2.10-92289ba21f-0e34a0e42d.zip b/.yarn/cache/@types-react-npm-19.2.10-92289ba21f-0e34a0e42d.zip deleted file mode 100644 index 04ebb8691..000000000 Binary files a/.yarn/cache/@types-react-npm-19.2.10-92289ba21f-0e34a0e42d.zip and /dev/null differ diff --git a/.yarn/cache/@types-react-npm-19.2.14-072ed0943f-fbff239089.zip b/.yarn/cache/@types-react-npm-19.2.14-072ed0943f-fbff239089.zip new file mode 100644 index 000000000..1bb3c6a64 Binary files /dev/null and b/.yarn/cache/@types-react-npm-19.2.14-072ed0943f-fbff239089.zip differ diff --git a/.yarn/cache/axios-npm-0.31.0-c3cc4d3e42-80536a915c.zip b/.yarn/cache/axios-npm-0.31.0-c3cc4d3e42-80536a915c.zip deleted file mode 100644 index 3ba926c5e..000000000 Binary files a/.yarn/cache/axios-npm-0.31.0-c3cc4d3e42-80536a915c.zip and /dev/null differ diff --git a/.yarn/cache/axios-npm-0.31.1-0574a0de7d-097dffdc0a.zip b/.yarn/cache/axios-npm-0.31.1-0574a0de7d-097dffdc0a.zip new file mode 100644 index 000000000..1db75a898 Binary files /dev/null and b/.yarn/cache/axios-npm-0.31.1-0574a0de7d-097dffdc0a.zip differ diff --git a/.yarn/cache/brace-expansion-npm-1.1.11-fb95eb05ad-faf34a7bb0.zip b/.yarn/cache/brace-expansion-npm-1.1.11-fb95eb05ad-faf34a7bb0.zip deleted file mode 100644 index 9deab64ad..000000000 Binary files a/.yarn/cache/brace-expansion-npm-1.1.11-fb95eb05ad-faf34a7bb0.zip and /dev/null differ diff --git a/.yarn/cache/brace-expansion-npm-1.1.14-a997f4f4e7-2de747a589.zip b/.yarn/cache/brace-expansion-npm-1.1.14-a997f4f4e7-2de747a589.zip new file mode 100644 index 000000000..809351b2e Binary files /dev/null and b/.yarn/cache/brace-expansion-npm-1.1.14-a997f4f4e7-2de747a589.zip differ diff --git a/.yarn/cache/braces-npm-3.0.2-782240b28a-966b1fb48d.zip b/.yarn/cache/braces-npm-3.0.2-782240b28a-966b1fb48d.zip deleted file mode 100644 index 4cf997e3b..000000000 Binary files a/.yarn/cache/braces-npm-3.0.2-782240b28a-966b1fb48d.zip and /dev/null differ diff --git a/.yarn/cache/braces-npm-3.0.3-582c14023c-fad11a0d46.zip b/.yarn/cache/braces-npm-3.0.3-582c14023c-fad11a0d46.zip new file mode 100644 index 000000000..bebc93863 Binary files /dev/null and b/.yarn/cache/braces-npm-3.0.3-582c14023c-fad11a0d46.zip differ diff --git a/.yarn/cache/fill-range-npm-7.0.1-b8b1817caa-e260f7592f.zip b/.yarn/cache/fill-range-npm-7.0.1-b8b1817caa-e260f7592f.zip deleted file mode 100644 index 7be5ed272..000000000 Binary files a/.yarn/cache/fill-range-npm-7.0.1-b8b1817caa-e260f7592f.zip and /dev/null differ diff --git a/.yarn/cache/fill-range-npm-7.1.1-bf491486db-a7095cb39e.zip b/.yarn/cache/fill-range-npm-7.1.1-bf491486db-a7095cb39e.zip new file mode 100644 index 000000000..2dbb57d46 Binary files /dev/null and b/.yarn/cache/fill-range-npm-7.1.1-bf491486db-a7095cb39e.zip differ diff --git a/.yarn/cache/flatted-npm-3.2.9-0462256d3c-dc2b89e46a.zip b/.yarn/cache/flatted-npm-3.2.9-0462256d3c-dc2b89e46a.zip deleted file mode 100644 index 4fd521e4e..000000000 Binary files a/.yarn/cache/flatted-npm-3.2.9-0462256d3c-dc2b89e46a.zip and /dev/null differ diff --git a/.yarn/cache/flatted-npm-3.4.2-e32280259b-a9e78fe5c2.zip b/.yarn/cache/flatted-npm-3.4.2-e32280259b-a9e78fe5c2.zip new file mode 100644 index 000000000..5028db149 Binary files /dev/null and b/.yarn/cache/flatted-npm-3.4.2-e32280259b-a9e78fe5c2.zip differ diff --git a/.yarn/cache/immutable-npm-4.3.5-5958499808-dbc1b8c808.zip b/.yarn/cache/immutable-npm-4.3.5-5958499808-dbc1b8c808.zip deleted file mode 100644 index 04edaa62d..000000000 Binary files a/.yarn/cache/immutable-npm-4.3.5-5958499808-dbc1b8c808.zip and /dev/null differ diff --git a/.yarn/cache/immutable-npm-4.3.8-9db2d34fb7-27a134cec0.zip b/.yarn/cache/immutable-npm-4.3.8-9db2d34fb7-27a134cec0.zip new file mode 100644 index 000000000..01303cb80 Binary files /dev/null and b/.yarn/cache/immutable-npm-4.3.8-9db2d34fb7-27a134cec0.zip differ diff --git a/.yarn/cache/js-yaml-npm-3.14.1-b968c6095e-9e22d80b4d.zip b/.yarn/cache/js-yaml-npm-3.14.1-b968c6095e-9e22d80b4d.zip deleted file mode 100644 index 431983ca8..000000000 Binary files a/.yarn/cache/js-yaml-npm-3.14.1-b968c6095e-9e22d80b4d.zip and /dev/null differ diff --git a/.yarn/cache/js-yaml-npm-3.14.2-debd9d20c3-172e0b6007.zip b/.yarn/cache/js-yaml-npm-3.14.2-debd9d20c3-172e0b6007.zip new file mode 100644 index 000000000..956951a43 Binary files /dev/null and b/.yarn/cache/js-yaml-npm-3.14.2-debd9d20c3-172e0b6007.zip differ diff --git a/.yarn/cache/lodash-es-npm-4.17.21-b45832dfce-03f39878ea.zip b/.yarn/cache/lodash-es-npm-4.18.1-02cf41b912-8bfad225ef.zip similarity index 84% rename from .yarn/cache/lodash-es-npm-4.17.21-b45832dfce-03f39878ea.zip rename to .yarn/cache/lodash-es-npm-4.18.1-02cf41b912-8bfad225ef.zip index dc6b4a19e..fa92ab9ad 100644 Binary files a/.yarn/cache/lodash-es-npm-4.17.21-b45832dfce-03f39878ea.zip and b/.yarn/cache/lodash-es-npm-4.18.1-02cf41b912-8bfad225ef.zip differ diff --git a/.yarn/cache/lodash-npm-4.17.21-6382451519-c08619c038.zip b/.yarn/cache/lodash-npm-4.18.1-a64c3070ac-306fea53df.zip similarity index 64% rename from .yarn/cache/lodash-npm-4.17.21-6382451519-c08619c038.zip rename to .yarn/cache/lodash-npm-4.18.1-a64c3070ac-306fea53df.zip index 5c76f21a6..dcdd540be 100644 Binary files a/.yarn/cache/lodash-npm-4.17.21-6382451519-c08619c038.zip and b/.yarn/cache/lodash-npm-4.18.1-a64c3070ac-306fea53df.zip differ diff --git a/.yarn/cache/minimatch-npm-3.1.2-9405269906-e0b25b04cd.zip b/.yarn/cache/minimatch-npm-3.1.2-9405269906-e0b25b04cd.zip deleted file mode 100644 index d3ea73278..000000000 Binary files a/.yarn/cache/minimatch-npm-3.1.2-9405269906-e0b25b04cd.zip and /dev/null differ diff --git a/.yarn/cache/minimatch-npm-3.1.5-86958baf50-b11a7ee577.zip b/.yarn/cache/minimatch-npm-3.1.5-86958baf50-b11a7ee577.zip new file mode 100644 index 000000000..b333f1e7f Binary files /dev/null and b/.yarn/cache/minimatch-npm-3.1.5-86958baf50-b11a7ee577.zip differ diff --git a/.yarn/cache/minimatch-npm-9.0.3-69d7d6fad5-c81b47d281.zip b/.yarn/cache/minimatch-npm-9.0.3-69d7d6fad5-c81b47d281.zip deleted file mode 100644 index dc6ab1689..000000000 Binary files a/.yarn/cache/minimatch-npm-9.0.3-69d7d6fad5-c81b47d281.zip and /dev/null differ diff --git a/.yarn/cache/next-npm-15.5.15-3f1140086c-f15867d9e0.zip b/.yarn/cache/next-npm-15.5.16-6a72a2b582-6575ffe4b6.zip similarity index 85% rename from .yarn/cache/next-npm-15.5.15-3f1140086c-f15867d9e0.zip rename to .yarn/cache/next-npm-15.5.16-6a72a2b582-6575ffe4b6.zip index 4c3f77d00..cd4fc6065 100644 Binary files a/.yarn/cache/next-npm-15.5.15-3f1140086c-f15867d9e0.zip and b/.yarn/cache/next-npm-15.5.16-6a72a2b582-6575ffe4b6.zip differ diff --git a/.yarn/cache/picomatch-npm-2.3.1-c782cfd986-60c2595003.zip b/.yarn/cache/picomatch-npm-2.3.1-c782cfd986-60c2595003.zip deleted file mode 100644 index dbf505d9a..000000000 Binary files a/.yarn/cache/picomatch-npm-2.3.1-c782cfd986-60c2595003.zip and /dev/null differ diff --git a/.yarn/cache/picomatch-npm-2.3.2-4d85543a37-b788ef8148.zip b/.yarn/cache/picomatch-npm-2.3.2-4d85543a37-b788ef8148.zip new file mode 100644 index 000000000..713853ac5 Binary files /dev/null and b/.yarn/cache/picomatch-npm-2.3.2-4d85543a37-b788ef8148.zip differ diff --git a/.yarn/cache/postcss-npm-8.4.47-2f4d4be1fa-f2b50ba9b6.zip b/.yarn/cache/postcss-npm-8.4.47-2f4d4be1fa-f2b50ba9b6.zip deleted file mode 100644 index f44c13d43..000000000 Binary files a/.yarn/cache/postcss-npm-8.4.47-2f4d4be1fa-f2b50ba9b6.zip and /dev/null differ diff --git a/.yarn/cache/postcss-npm-8.5.14-1cf8d01c78-2e3f4dea69.zip b/.yarn/cache/postcss-npm-8.5.14-1cf8d01c78-2e3f4dea69.zip new file mode 100644 index 000000000..97d73ad8d Binary files /dev/null and b/.yarn/cache/postcss-npm-8.5.14-1cf8d01c78-2e3f4dea69.zip differ diff --git a/.yarn/cache/react-dom-npm-19.2.5-94c3f138c3-ba14b022c7.zip b/.yarn/cache/react-dom-npm-19.2.6-6f68db79da-695122e37d.zip similarity index 90% rename from .yarn/cache/react-dom-npm-19.2.5-94c3f138c3-ba14b022c7.zip rename to .yarn/cache/react-dom-npm-19.2.6-6f68db79da-695122e37d.zip index a60974cff..82f8ae7fd 100644 Binary files a/.yarn/cache/react-dom-npm-19.2.5-94c3f138c3-ba14b022c7.zip and b/.yarn/cache/react-dom-npm-19.2.6-6f68db79da-695122e37d.zip differ diff --git a/.yarn/cache/react-npm-19.2.5-1e4f979df2-1c3c7ffecb.zip b/.yarn/cache/react-npm-19.2.6-f326db5d50-205f0db93b.zip similarity index 59% rename from .yarn/cache/react-npm-19.2.5-1e4f979df2-1c3c7ffecb.zip rename to .yarn/cache/react-npm-19.2.6-f326db5d50-205f0db93b.zip index c5b0ee134..e7fd508bc 100644 Binary files a/.yarn/cache/react-npm-19.2.5-1e4f979df2-1c3c7ffecb.zip and b/.yarn/cache/react-npm-19.2.6-f326db5d50-205f0db93b.zip differ diff --git a/.yarn/cache/regenerator-runtime-npm-0.14.1-a6c97c609a-5db3161abb.zip b/.yarn/cache/regenerator-runtime-npm-0.14.1-a6c97c609a-5db3161abb.zip deleted file mode 100644 index 176e75507..000000000 Binary files a/.yarn/cache/regenerator-runtime-npm-0.14.1-a6c97c609a-5db3161abb.zip and /dev/null differ diff --git a/.yarn/cache/sax-npm-1.6.0-39dc3ef158-0909cedcd9.zip b/.yarn/cache/sax-npm-1.6.0-39dc3ef158-0909cedcd9.zip new file mode 100644 index 000000000..a6b16e21a Binary files /dev/null and b/.yarn/cache/sax-npm-1.6.0-39dc3ef158-0909cedcd9.zip differ diff --git a/.yarn/cache/svgo-npm-3.3.2-69e1d32944-82fdea9b93.zip b/.yarn/cache/svgo-npm-3.3.2-69e1d32944-82fdea9b93.zip deleted file mode 100644 index d88dbd02c..000000000 Binary files a/.yarn/cache/svgo-npm-3.3.2-69e1d32944-82fdea9b93.zip and /dev/null differ diff --git a/.yarn/cache/svgo-npm-3.3.3-f4851edd74-f3c1b4d05d.zip b/.yarn/cache/svgo-npm-3.3.3-f4851edd74-f3c1b4d05d.zip new file mode 100644 index 000000000..652f32b6a Binary files /dev/null and b/.yarn/cache/svgo-npm-3.3.3-f4851edd74-f3c1b4d05d.zip differ diff --git a/.yarn/cache/tar-npm-6.2.0-3eb25205a7-2042bbb148.zip b/.yarn/cache/tar-npm-6.2.1-237800bb20-bfbfbb2861.zip similarity index 62% rename from .yarn/cache/tar-npm-6.2.0-3eb25205a7-2042bbb148.zip rename to .yarn/cache/tar-npm-6.2.1-237800bb20-bfbfbb2861.zip index 194ce1253..066f40476 100644 Binary files a/.yarn/cache/tar-npm-6.2.0-3eb25205a7-2042bbb148.zip and b/.yarn/cache/tar-npm-6.2.1-237800bb20-bfbfbb2861.zip differ diff --git a/package.json b/package.json index 7eee4e33b..ab47fc535 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "@next/third-parties": "latest", "@sentry/nextjs": "^10", "@tanstack/react-query": "^5.90.21", - "axios": "^0.31.0", + "axios": "^0.31.1", "dayjs": "^1.11.12", "embla-carousel-autoplay": "^8.0.4", "embla-carousel-react": "^8.0.4", @@ -18,9 +18,9 @@ "html2canvas": "^1.4.1", "idb": "^8.0.3", "lottie-react": "^2.4.1", - "next": "15.5.15", - "react": "^19.2.5", - "react-dom": "^19.2.5", + "next": "15.5.16", + "react": "^19.2.6", + "react-dom": "^19.2.6", "react-hook-form": "^7.55.0", "react-quill": "^2.0.0", "react-quill-new": "^3.6.0", @@ -67,7 +67,7 @@ "@types/jest": "^27.0.1", "@types/navermaps": "^3.6.4", "@types/node": "^16.7.13", - "@types/react": "^19.2.10", + "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@types/react-window": "^1.8.5", "dotenv": "^17.2.3", @@ -81,7 +81,7 @@ "eslint-plugin-react-hooks": "^7.0.1", "globals": "^16.4.0", "jest": "^29.7.0", - "postcss": "^8.4.47", + "postcss": "^8.5.11", "prettier": "^3.6.2", "sass": "^1.53.0", "stylelint": "^14.9.1", diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 000000000..5ff9868e9 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,7 @@ +sonar.projectKey=BCSDLab_KOIN_WEB_RECODE +sonar.organization=bcsdlab + +sonar.sources=src +sonar.exclusions=src/generated/**,**/*.test.ts,**/*.test.tsx,**/__tests__/** + +sonar.typescript.tsconfigPath=tsconfig.json diff --git a/src/api/callvan/APIDetail.ts b/src/api/callvan/APIDetail.ts index 1fa249767..f9db5412c 100644 --- a/src/api/callvan/APIDetail.ts +++ b/src/api/callvan/APIDetail.ts @@ -5,6 +5,7 @@ import { CallvanListResponse, CallvanNotificationsResponse, CallvanPostDetail, + CallvanRestrictionResponse, CallvanReportRequest, CreateCallvanRequest, CreateCallvanResponse, @@ -42,6 +43,18 @@ export class GetCallvanNotifications imp constructor(public authorization: string) {} } +export class GetCallvanRestriction implements APIRequest { + method = HTTP_METHOD.GET; + + path = '/callvan/restriction'; + + response!: R; + + auth = true; + + constructor(public authorization: string) {} +} + export class PostMarkAllNotificationsRead implements APIRequest { method = HTTP_METHOD.POST; diff --git a/src/api/callvan/entity.ts b/src/api/callvan/entity.ts index 5bdacb133..85030fb5d 100644 --- a/src/api/callvan/entity.ts +++ b/src/api/callvan/entity.ts @@ -74,6 +74,22 @@ export interface CallvanListResponse extends APIResponse { total_page: number; } +export type CallvanRestrictionType = 'TEMPORARY_RESTRICTION_14_DAYS' | 'PERMANENT_RESTRICTION'; + +export interface UnrestrictedCallvanResponse { + is_restricted: false; + restriction_type: null; + restricted_until: null; +} + +export interface RestrictedCallvanResponse { + is_restricted: true; + restriction_type: CallvanRestrictionType; + restricted_until: string; +} + +export type CallvanRestrictionResponse = UnrestrictedCallvanResponse | RestrictedCallvanResponse; + export type CallvanNotificationType = | 'RECRUITMENT_COMPLETE' | 'NEW_MESSAGE' diff --git a/src/api/callvan/index.ts b/src/api/callvan/index.ts index c74248975..024aa6124 100644 --- a/src/api/callvan/index.ts +++ b/src/api/callvan/index.ts @@ -6,6 +6,7 @@ import { GetCallvanList, GetCallvanNotifications, GetCallvanPostDetail, + GetCallvanRestriction, PostCallvan, PostCallvanReport, PostCallvanChat, @@ -20,6 +21,7 @@ import { export const getCallvanList = APIClient.of(GetCallvanList); export const getCallvanPostDetail = APIClient.of(GetCallvanPostDetail); export const getCallvanNotifications = APIClient.of(GetCallvanNotifications); +export const getCallvanRestriction = APIClient.of(GetCallvanRestriction); export const markAllNotificationsRead = APIClient.of(PostMarkAllNotificationsRead); export const markNotificationRead = APIClient.of(PostMarkNotificationRead); export const deleteAllNotifications = APIClient.of(DeleteAllNotifications); diff --git a/src/api/callvan/mutations.ts b/src/api/callvan/mutations.ts index 34fdf3ac4..92cca7ab4 100644 --- a/src/api/callvan/mutations.ts +++ b/src/api/callvan/mutations.ts @@ -19,7 +19,7 @@ const invalidateCallvanInfiniteList = (queryClient: QueryClient) => queryClient.invalidateQueries({ queryKey: callvanQueryKeys.infiniteListRoot }); const invalidateCallvanNotifications = (queryClient: QueryClient) => - queryClient.invalidateQueries({ queryKey: callvanQueryKeys.notifications }); + queryClient.invalidateQueries({ queryKey: ['callvan', 'notifications'] }); export const callvanMutations = { create: (queryClient: QueryClient, token: string) => diff --git a/src/api/callvan/queries.ts b/src/api/callvan/queries.ts index 1ca9fe369..d3d7754af 100644 --- a/src/api/callvan/queries.ts +++ b/src/api/callvan/queries.ts @@ -1,6 +1,6 @@ import { infiniteQueryOptions, queryOptions } from '@tanstack/react-query'; import { CallvanListRequest } from './entity'; -import { getCallvanChat, getCallvanList, getCallvanNotifications, getCallvanPostDetail } from './index'; +import { getCallvanChat, getCallvanList, getCallvanNotifications, getCallvanPostDetail, getCallvanRestriction } from './index'; const CALLVAN_LIST_LIMIT = 10; @@ -12,7 +12,8 @@ export const callvanQueryKeys = { list: (params: CallvanListRequest) => [...callvanQueryKeys.listRoot, params] as const, infiniteListRoot: ['callvan', 'infinite-list'] as const, infiniteList: (params: CallvanInfiniteListParams) => [...callvanQueryKeys.infiniteListRoot, params] as const, - notifications: ['callvan', 'notifications'] as const, + notifications: (token: string) => ['callvan', 'notifications', token] as const, + restriction: (token: string) => ['callvan', 'restriction', token] as const, postDetail: (postId: number) => ['callvan', 'post-detail', postId] as const, chat: (postId: number) => ['callvan', 'chat', postId] as const, }; @@ -45,8 +46,16 @@ export const callvanQueries = { notifications: (token: string) => queryOptions({ - queryKey: callvanQueryKeys.notifications, + queryKey: callvanQueryKeys.notifications(token), queryFn: () => getCallvanNotifications(token), + staleTime: 60000, + }), + + restriction: (token: string) => + queryOptions({ + queryKey: callvanQueryKeys.restriction(token), + queryFn: () => getCallvanRestriction(token), + staleTime: 0, }), postDetail: (token: string, postId: number) => diff --git a/src/api/coopshop/entity.ts b/src/api/coopshop/entity.ts index f9f55322e..d05e015fb 100644 --- a/src/api/coopshop/entity.ts +++ b/src/api/coopshop/entity.ts @@ -24,5 +24,6 @@ export interface CoopShopDetailResponse extends APIResponse { phone: string | null; location: string; remarks: string | null; + icon_url: string | null; updated_at: string; // yyyy-MM-dd } diff --git a/src/components/Callvan/components/AddPostForm/index.tsx b/src/components/Callvan/components/AddPostForm/index.tsx index c2ac7980d..d563b2be7 100644 --- a/src/components/Callvan/components/AddPostForm/index.tsx +++ b/src/components/Callvan/components/AddPostForm/index.tsx @@ -91,7 +91,12 @@ export default function AddPostForm() { if (!form.departureType || !form.arrivalType || isPending) return; const selectedDateTime = new Date(form.departureDate); - const hour24 = form.isPM ? (form.departureHour === 12 ? 12 : form.departureHour + 12) : form.departureHour === 12 ? 0 : form.departureHour; + let hour24: number; + if (form.isPM) { + hour24 = form.departureHour === 12 ? 12 : form.departureHour + 12; + } else { + hour24 = form.departureHour === 12 ? 0 : form.departureHour; + } selectedDateTime.setHours(hour24, form.departureMinute, 0, 0); if (selectedDateTime < new Date()) { diff --git a/src/components/Callvan/components/CallvanPageLayout/index.tsx b/src/components/Callvan/components/CallvanPageLayout/index.tsx index 96217492b..3109f23b0 100644 --- a/src/components/Callvan/components/CallvanPageLayout/index.tsx +++ b/src/components/Callvan/components/CallvanPageLayout/index.tsx @@ -84,7 +84,7 @@ export default function CallvanPageLayout({ departures: filter.departures.length > 0 ? filter.departures.join(',') : undefined, arrivals: filter.arrivals.length > 0 ? filter.arrivals.join(',') : undefined, sort: filter.sort !== 'LATEST_DESC' ? filter.sort : undefined, - author: filter.author !== 'ALL' ? filter.author : undefined, + author: filter.author === 'ALL' ? undefined : filter.author, joined: filter.joined ? 'true' : undefined, page: undefined, }, diff --git a/src/components/Callvan/components/CallvanRestrictionModal/CallvanRestrictionModal.module.scss b/src/components/Callvan/components/CallvanRestrictionModal/CallvanRestrictionModal.module.scss new file mode 100644 index 000000000..2e79cdf7f --- /dev/null +++ b/src/components/Callvan/components/CallvanRestrictionModal/CallvanRestrictionModal.module.scss @@ -0,0 +1,97 @@ +$callvan-restriction-accent-color: #b611f5; +$callvan-restriction-font-family: var(--font-pretendard), sans-serif; + +.modal { + &__overlay { + position: fixed; + inset: 0; + z-index: 300; + display: flex; + align-items: center; + justify-content: center; + padding: 24px; + box-sizing: border-box; + } + + &__dim { + position: absolute; + inset: 0; + border: none; + background: rgb(0 0 0 / 70%); + padding: 0; + cursor: pointer; + } + + &__sheet { + position: relative; + z-index: 301; + width: min(301px, 100%); + border-radius: 8px; + background: #fff; + box-shadow: + 0 1px 2px rgb(0 0 0 / 4%), + 0 4px 5px rgb(0 0 0 / 8%); + } + + &__content { + display: flex; + flex-direction: column; + gap: 24px; + align-items: center; + padding: 24px 32px; + } + + &__text { + display: flex; + flex-direction: column; + gap: 8px; + align-items: center; + width: 100%; + text-align: center; + } + + &__title { + margin: 0; + color: #4b4b4b; + font-family: $callvan-restriction-font-family; + font-size: 18px; + font-weight: 500; + line-height: 1.6; + } + + &__accent { + color: $callvan-restriction-accent-color; + } + + &__description { + display: flex; + flex-direction: column; + width: 100%; + color: #727272; + font-family: $callvan-restriction-font-family; + font-size: 14px; + font-weight: 400; + line-height: 1.6; + + p { + margin: 0; + } + } + + &__button { + display: flex; + width: 100%; + align-items: center; + justify-content: center; + border: none; + border-radius: 8px; + background: $callvan-restriction-accent-color; + padding: 12px; + color: #fff; + font-family: $callvan-restriction-font-family; + font-size: 15px; + font-weight: 500; + line-height: 1.6; + cursor: pointer; + } +} diff --git a/src/components/Callvan/components/CallvanRestrictionModal/index.tsx b/src/components/Callvan/components/CallvanRestrictionModal/index.tsx new file mode 100644 index 000000000..d10b443cf --- /dev/null +++ b/src/components/Callvan/components/CallvanRestrictionModal/index.tsx @@ -0,0 +1,54 @@ +import { useEffect } from 'react'; +import { RestrictedCallvanResponse } from 'api/callvan/entity'; +import { getCallvanRestrictionModalCopy } from 'components/Callvan/utils/callvanRestriction'; +import { useBodyScrollLock } from 'utils/hooks/ui/useBodyScrollLock'; +import styles from './CallvanRestrictionModal.module.scss'; + +interface CallvanRestrictionModalProps { + readonly restriction: RestrictedCallvanResponse | null; + readonly onClose: () => void; +} + +export default function CallvanRestrictionModal({ restriction, onClose }: CallvanRestrictionModalProps) { + useBodyScrollLock(); + + const { titleAccent, titleRest, descriptionLines } = getCallvanRestrictionModalCopy(restriction); + + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + onClose(); + } + }; + + document.addEventListener('keydown', handleKeyDown); + + return () => { + document.removeEventListener('keydown', handleKeyDown); + }; + }, [onClose]); + + return ( + + + + + + ); +} diff --git a/src/components/Callvan/hooks/useCallvanRestrictionModal.tsx b/src/components/Callvan/hooks/useCallvanRestrictionModal.tsx new file mode 100644 index 000000000..e6444bfdc --- /dev/null +++ b/src/components/Callvan/hooks/useCallvanRestrictionModal.tsx @@ -0,0 +1,47 @@ +import { useCallback } from 'react'; +import { sendClientError } from '@bcsdlab/koin'; +import { useQueryClient } from '@tanstack/react-query'; +import { RestrictedCallvanResponse } from 'api/callvan/entity'; +import { callvanQueries } from 'api/callvan/queries'; +import CallvanRestrictionModal from 'components/Callvan/components/CallvanRestrictionModal'; +import { isCallvanRestrictedError } from 'components/Callvan/utils/callvanRestriction'; +import { Portal } from 'components/modal/Modal/PortalProvider'; +import useModalPortal from 'utils/hooks/layout/useModalPortal'; + +export default function useCallvanRestrictionModal(token: string) { + const portalManager = useModalPortal(); + const queryClient = useQueryClient(); + + const open = useCallback( + (restriction: RestrictedCallvanResponse) => { + portalManager.open((portalOption: Portal) => ( + + )); + }, + [portalManager], + ); + + const openFromError = useCallback( + async (error: unknown) => { + if (!isCallvanRestrictedError(error)) return false; + + if (!token) return false; + + try { + const restriction = await queryClient.fetchQuery(callvanQueries.restriction(token)); + if (restriction.is_restricted) { + open(restriction); + return true; + } + + return false; + } catch (restrictionError) { + sendClientError(restrictionError); + return false; + } + }, + [open, queryClient, token], + ); + + return { openFromError }; +} diff --git a/src/components/Callvan/hooks/useCreateCallvan.ts b/src/components/Callvan/hooks/useCreateCallvan.ts index f4c8072b2..a10326438 100644 --- a/src/components/Callvan/hooks/useCreateCallvan.ts +++ b/src/components/Callvan/hooks/useCreateCallvan.ts @@ -1,6 +1,7 @@ import { isKoinError, sendClientError } from '@bcsdlab/koin'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { callvanMutations } from 'api/callvan/mutations'; +import useCallvanRestrictionModal from 'components/Callvan/hooks/useCallvanRestrictionModal'; import useTokenState from 'utils/hooks/state/useTokenState'; import showToast from 'utils/ts/showToast'; @@ -8,10 +9,17 @@ const useCreateCallvan = () => { const token = useTokenState(); const queryClient = useQueryClient(); const mutation = callvanMutations.create(queryClient, token); + const { openFromError } = useCallvanRestrictionModal(token); const { mutate, isPending } = useMutation({ ...mutation, - onError: (e) => { + onError: async (e) => { + try { + if (await openFromError(e)) return; + } catch (restrictionError) { + sendClientError(restrictionError); + } + if (isKoinError(e)) { showToast('error', e.message || '게시글 작성에 실패했습니다.'); } else { diff --git a/src/components/Callvan/hooks/useJoinCallvan.ts b/src/components/Callvan/hooks/useJoinCallvan.ts index 6e313af31..4acd44c7b 100644 --- a/src/components/Callvan/hooks/useJoinCallvan.ts +++ b/src/components/Callvan/hooks/useJoinCallvan.ts @@ -1,6 +1,7 @@ import { isKoinError, sendClientError } from '@bcsdlab/koin'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { callvanMutations } from 'api/callvan/mutations'; +import useCallvanRestrictionModal from 'components/Callvan/hooks/useCallvanRestrictionModal'; import useTokenState from 'utils/hooks/state/useTokenState'; import showToast from 'utils/ts/showToast'; @@ -8,6 +9,7 @@ const useJoinCallvan = () => { const token = useTokenState(); const queryClient = useQueryClient(); const mutation = callvanMutations.join(queryClient, token); + const { openFromError } = useCallvanRestrictionModal(token); const { mutate, isPending } = useMutation({ ...mutation, @@ -15,7 +17,13 @@ const useJoinCallvan = () => { await mutation.onSuccess?.(...args); showToast('success', '참여가 완료되었습니다.'); }, - onError: (e) => { + onError: async (e) => { + try { + if (await openFromError(e)) return; + } catch (restrictionError) { + sendClientError(restrictionError); + } + if (isKoinError(e)) { showToast('error', e.message || '참여에 실패했습니다.'); } else { diff --git a/src/components/Callvan/utils/callvanRestriction.ts b/src/components/Callvan/utils/callvanRestriction.ts new file mode 100644 index 000000000..227643fa8 --- /dev/null +++ b/src/components/Callvan/utils/callvanRestriction.ts @@ -0,0 +1,61 @@ +import { isKoinError } from '@bcsdlab/koin'; +import { CallvanRestrictionResponse } from 'api/callvan/entity'; + +const CALLVAN_RESTRICTED_ERROR_STATUS = 403; + +interface CallvanRestrictionModalCopy { + titleAccent: string; + titleRest: string; + descriptionLines: string[]; +} + +const restrictionDateFormatter = new Intl.DateTimeFormat('ko-KR', { + month: 'long', + day: 'numeric', + timeZone: 'Asia/Seoul', +}); + +export function isCallvanRestrictedError(error: unknown): boolean { + if (!isKoinError(error)) return false; + + return error.status === CALLVAN_RESTRICTED_ERROR_STATUS; +} + +function formatRestrictedUntil(restrictedUntil: string | null): string | null { + if (!restrictedUntil) return null; + + const date = new Date(restrictedUntil); + if (Number.isNaN(date.getTime())) return null; + + return `${restrictionDateFormatter.format(date)}까지`; +} + +export function getCallvanRestrictionModalCopy( + restriction: CallvanRestrictionResponse | null, +): CallvanRestrictionModalCopy { + if (restriction?.restriction_type === 'TEMPORARY_RESTRICTION_14_DAYS') { + const formattedDate = formatRestrictedUntil(restriction.restricted_until); + + return { + titleAccent: '14일', + titleRest: ' 이용 정지', + descriptionLines: formattedDate + ? [`해당 계정은 ${formattedDate}`, '콜밴팟 기능을 사용할 수 없습니다.'] + : ['해당 계정은 콜밴팟 기능을', '사용할 수 없습니다.'], + }; + } + + if (restriction?.restriction_type === 'PERMANENT_RESTRICTION') { + return { + titleAccent: '영구', + titleRest: ' 정지', + descriptionLines: ['해당 계정은 콜밴팟 기능을', '사용할 수 없습니다.'], + }; + } + + return { + titleAccent: '이용', + titleRest: ' 제한', + descriptionLines: ['해당 계정은 콜밴팟 기능을', '사용할 수 없습니다.'], + }; +} diff --git a/src/components/CampusInfo/CampusInfo.module.scss b/src/components/CampusInfo/CampusInfo.module.scss index aeda0359a..98f4c4865 100644 --- a/src/components/CampusInfo/CampusInfo.module.scss +++ b/src/components/CampusInfo/CampusInfo.module.scss @@ -86,14 +86,34 @@ } .icon-wrapper { + align-items: center; border-radius: 8px; background: #f1f8ff; + display: flex; + flex-shrink: 0; + justify-content: center; padding: 12px; width: 32px; height: 32px; margin: 0; } +.icon-image { + display: block; + height: 100%; + object-fit: contain; + width: 100%; +} + +.icon-fallback { + color: #072552; + font-family: Pretendard, sans-serif; + font-size: 18px; + font-style: normal; + font-weight: 700; + line-height: 1; +} + .info-title-container { display: flex; gap: 16px; diff --git a/src/components/CampusInfo/index.tsx b/src/components/CampusInfo/index.tsx index 09eb07120..ef4af2765 100644 --- a/src/components/CampusInfo/index.tsx +++ b/src/components/CampusInfo/index.tsx @@ -1,14 +1,6 @@ import { cn } from '@bcsdlab/utils'; import { useSuspenseQuery } from '@tanstack/react-query'; import { coopshopQueries } from 'api/coopshop/queries'; -import Book from './svg/book.svg'; -import Cafe from './svg/cafe.svg'; -import Cut from './svg/cut.svg'; -import Flatware from './svg/flatware.svg'; -import Glasses from './svg/glasses.svg'; -import Laundry from './svg/laundry.svg'; -import PostOffice from './svg/post-office.svg'; -import Print from './svg/print.svg'; import styles from './CampusInfo.module.scss'; const CAFETERIA_HEAD_TABLE = { @@ -16,21 +8,6 @@ const CAFETERIA_HEAD_TABLE = { col: ['아침', '점심', '저녁'], }; -const SHOP_ICON = { - 서점: , - 대즐: , - 미용실: , - 세탁소: , - 우체국: , - '복지관 참빛관 편의점': , - 복사실: , - 학생식당: , - 복지관식당: , - 오락실: , - 안경원: , - 우편취급국: , -}; - const formatDateRange = (fromDate: string, toDate: string) => { const from = new Date(fromDate); const to = new Date(toDate); @@ -56,6 +33,27 @@ const formatDateRange = (fromDate: string, toDate: string) => { return `기간 : ${fromFormatted} - ${toFormatted}`; }; +type ShopIconProps = { + readonly iconUrl: string | null | undefined; + readonly name: string; +}; + +function ShopIcon({ iconUrl, name }: ShopIconProps) { + return ( +
+ {iconUrl ? ( + // NOTE: 백엔드가 내려주는 소형 반복 아이콘은 호스트가 고정되지 않을 수 있어 를 유지합니다. + // eslint-disable-next-line @next/next/no-img-element + {name} + ) : ( + + )} +
+ ); +} + function CampusInfo() { const { data: campusInfo } = useSuspenseQuery(coopshopQueries.allShopInfo()); @@ -87,10 +85,8 @@ function CampusInfo() {
-
- -
-
학생식당
+ +
{cafeteriaInfo?.name ?? '학생식당'}
@@ -125,9 +121,9 @@ function CampusInfo() { - {filteredCampusInfo.slice(0, 2).map(({ id, name, opens }) => ( + {filteredCampusInfo.slice(0, 2).map(({ id, name, opens, icon_url }) => (
-
{SHOP_ICON[name as keyof typeof SHOP_ICON]}
+
{name}
{opens.map(({ day_of_week, open_time, close_time }) => ( @@ -141,9 +137,9 @@ function CampusInfo() { ))}
- {filteredCampusInfo.slice(2, 6).map(({ id, name, opens }) => ( + {filteredCampusInfo.slice(2, 6).map(({ id, name, opens, icon_url }) => (
-
{SHOP_ICON[name as keyof typeof SHOP_ICON]}
+
{name}
{opens.map(({ day_of_week, open_time, close_time }) => ( @@ -157,9 +153,9 @@ function CampusInfo() { ))}
- {filteredCampusInfo.slice(6).map(({ id, name, opens }) => ( + {filteredCampusInfo.slice(6).map(({ id, name, opens, icon_url }) => (
-
{SHOP_ICON[name as keyof typeof SHOP_ICON]}
+
{name}
{opens.map(({ day_of_week, open_time, close_time }) => ( diff --git a/src/components/TimetablePage/components/MainTimetable/index.tsx b/src/components/TimetablePage/components/MainTimetable/index.tsx index 9342bd694..bd78f8b4a 100644 --- a/src/components/TimetablePage/components/MainTimetable/index.tsx +++ b/src/components/TimetablePage/components/MainTimetable/index.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { useRouter } from 'next/router'; import { useSuspenseQuery } from '@tanstack/react-query'; import { deptQueries } from 'api/dept/queries'; -import { Lecture, MyLectureInfo } from 'api/timetable/entity'; +import { Lecture, MyLectureInfo, SemesterCheckResponse, TimetableFrameListResponse } from 'api/timetable/entity'; import { isValidTimetableFrameId } from 'api/timetable/queries'; import DownloadIcon from 'assets/svg/download-icon.svg'; import GraduationIcon from 'assets/svg/graduation-icon.svg'; @@ -24,13 +24,30 @@ import DownloadTimetableModal from './DownloadTimetableModal'; import styles from './MyLectureTimetable.module.scss'; interface MainTimetableLayoutProps { - curriculum: React.ReactNode; - myLectures: Lecture[] | MyLectureInfo[] | undefined; - onClickDownloadImage: (e: React.MouseEvent) => void; - onClickEdit: () => void; - onClickGraduation: () => void; - timetableContent: React.ReactNode; - footer?: React.ReactNode; + readonly curriculum: React.ReactNode; + readonly myLectures: Lecture[] | MyLectureInfo[] | undefined; + readonly onClickDownloadImage: (e: React.MouseEvent) => void; + readonly onClickEdit: () => void; + readonly onClickGraduation: () => void; + readonly timetableContent: React.ReactNode; + readonly footer?: React.ReactNode; +} + +function checkSemesterAndTimetable( + mySemester: SemesterCheckResponse | null | undefined, + frameList: TimetableFrameListResponse, +): boolean { + if (mySemester?.semesters.length === 0) { + toast.error('학기가 존재하지 않습니다. 학기를 추가해주세요.'); + return false; + } + + if (!frameList.some((frame) => isValidTimetableFrameId(frame.id))) { + toast.error('시간표가 존재하지 않습니다. 시간표를 추가해주세요.'); + return false; + } + + return true; } function MainTimetableLayout({ @@ -79,27 +96,13 @@ function InvalidMainTimetable() { const { data: deptList } = useSuspenseQuery(deptQueries.list()); const { data: mySemester } = useSemesterCheck(token); - const isSemesterAndTimetableExist = () => { - if (mySemester?.semesters.length === 0) { - toast.error('학기가 존재하지 않습니다. 학기를 추가해주세요.'); - return false; - } - - if (!timeTableFrameList.some((frame) => isValidTimetableFrameId(frame.id))) { - toast.error('시간표가 존재하지 않습니다. 시간표를 추가해주세요.'); - return false; - } - - return true; - }; - const onClickDownloadImage = (e: React.MouseEvent) => { e.stopPropagation(); - isSemesterAndTimetableExist(); + checkSemesterAndTimetable(mySemester, timeTableFrameList); }; const onClickEdit = () => { - isSemesterAndTimetableExist(); + checkSemesterAndTimetable(mySemester, timeTableFrameList); }; return ( @@ -121,7 +124,7 @@ function InvalidMainTimetable() { ); } -function ValidMainTimetable({ timetableFrameId }: { timetableFrameId: number }) { +function ValidMainTimetable({ timetableFrameId }: { readonly timetableFrameId: number }) { const [isModalOpen, openModal, closeModal] = useBooleanState(false); const token = useTokenState(); const semester = useSemester(); @@ -132,24 +135,10 @@ function ValidMainTimetable({ timetableFrameId }: { timetableFrameId: number }) const { data: deptList } = useSuspenseQuery(deptQueries.list()); const { data: mySemester } = useSemesterCheck(token); - const isSemesterAndTimetableExist = () => { - if (mySemester?.semesters.length === 0) { - toast.error('학기가 존재하지 않습니다. 학기를 추가해주세요.'); - return false; - } - - if (!timeTableFrameList.some((frame) => isValidTimetableFrameId(frame.id))) { - toast.error('시간표가 존재하지 않습니다. 시간표를 추가해주세요.'); - return false; - } - - return true; - }; - const onClickDownloadImage = (e: React.MouseEvent) => { e.stopPropagation(); - if (isSemesterAndTimetableExist()) { + if (checkSemesterAndTimetable(mySemester, timeTableFrameList)) { logger.actionEventClick({ team: 'USER', event_label: 'timetable', @@ -161,7 +150,7 @@ function ValidMainTimetable({ timetableFrameId }: { timetableFrameId: number }) }; const onClickEdit = () => { - if (isSemesterAndTimetableExist()) { + if (checkSemesterAndTimetable(mySemester, timeTableFrameList)) { router.push( `/${ROUTES.TimetableModify({ id: String(timetableFrameId), type: 'regular' })}&year=${semester?.year}&term=${semester?.term}`, ); @@ -190,7 +179,7 @@ function ValidMainTimetable({ timetableFrameId }: { timetableFrameId: number }) ); } -function MainTimetable({ timetableFrameId }: { timetableFrameId: number }) { +function MainTimetable({ timetableFrameId }: { readonly timetableFrameId: number }) { if (!isValidTimetableFrameId(timetableFrameId)) { return ; } diff --git a/src/components/TimetablePage/components/TimetableGridPlaceholder/index.tsx b/src/components/TimetablePage/components/TimetableGridPlaceholder/index.tsx index fc9735f50..47c37cd3c 100644 --- a/src/components/TimetablePage/components/TimetableGridPlaceholder/index.tsx +++ b/src/components/TimetablePage/components/TimetableGridPlaceholder/index.tsx @@ -8,10 +8,10 @@ const DEFAULT_TIME_STRING = ['9', '10', '11', '12', '13', '14', '15', '16', '17' ]); interface TimetableGridPlaceholderProps { - firstColumnWidth: number; - columnWidth: number; - rowHeight: number; - totalHeight: number; + readonly firstColumnWidth: number; + readonly columnWidth: number; + readonly rowHeight: number; + readonly totalHeight: number; } export default function TimetableGridPlaceholder({ diff --git a/src/components/cafeteria/MobileCafeteriaPage/MobileCafeteriaPage.module.scss b/src/components/cafeteria/MobileCafeteriaPage/MobileCafeteriaPage.module.scss index 1e3d9d4e5..175a71bed 100644 --- a/src/components/cafeteria/MobileCafeteriaPage/MobileCafeteriaPage.module.scss +++ b/src/components/cafeteria/MobileCafeteriaPage/MobileCafeteriaPage.module.scss @@ -71,7 +71,7 @@ border-radius: 16px; display: flex; align-items: center; - margin-top: 12px; + margin: 16px 24px; box-shadow: 0 2px 4px rgb(0 0 0 / 4%), 0 1px 1px rgb(0 0 0 / 2%); @@ -91,4 +91,8 @@ line-height: 160%; font-weight: 600; } + + &__arrow { + margin-left: auto; + } } diff --git a/src/components/cafeteria/MobileCafeteriaPage/components/MobileDiningBlocks/MobileDiningBlocks.module.scss b/src/components/cafeteria/MobileCafeteriaPage/components/MobileDiningBlocks/MobileDiningBlocks.module.scss index 70a27489b..5cf133823 100644 --- a/src/components/cafeteria/MobileCafeteriaPage/components/MobileDiningBlocks/MobileDiningBlocks.module.scss +++ b/src/components/cafeteria/MobileCafeteriaPage/components/MobileDiningBlocks/MobileDiningBlocks.module.scss @@ -1,7 +1,9 @@ .category { display: block; - margin: 0; background-color: #fff; + margin: 0 24px 16px; + border-radius: 16px; + overflow: hidden; &__menu-list-row { display: flex; diff --git a/src/pages/callvan/index.tsx b/src/pages/callvan/index.tsx index e7c266f84..79a1530c1 100644 --- a/src/pages/callvan/index.tsx +++ b/src/pages/callvan/index.tsx @@ -50,7 +50,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => queryClient.prefetchInfiniteQuery(callvanQueries.infiniteList(token ?? '', apiParams)), token ? queryClient.prefetchQuery(callvanQueries.notifications(token)) - : queryClient.setQueryData(callvanQueryKeys.notifications, []), + : queryClient.setQueryData(callvanQueryKeys.notifications(''), []), ]); } catch (error) { console.error('[SSR] callvan prefetch failed:', error); diff --git a/src/pages/callvan/notifications/index.tsx b/src/pages/callvan/notifications/index.tsx index 404c25107..2b15049a2 100644 --- a/src/pages/callvan/notifications/index.tsx +++ b/src/pages/callvan/notifications/index.tsx @@ -27,7 +27,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => if (token) { await queryClient.prefetchQuery(callvanQueries.notifications(token)); } else { - queryClient.setQueryData(callvanQueryKeys.notifications, []); + queryClient.setQueryData(callvanQueryKeys.notifications(''), []); } } catch (error) { console.error('[SSR] callvan notifications prefetch failed:', error); diff --git a/src/pages/campusinfo/index.tsx b/src/pages/campusinfo/index.tsx index b2d8212c7..d0b23e470 100644 --- a/src/pages/campusinfo/index.tsx +++ b/src/pages/campusinfo/index.tsx @@ -1,180 +1 @@ -import { cn } from '@bcsdlab/utils'; -import { useSuspenseQuery } from '@tanstack/react-query'; -import { coopshopQueries } from 'api/coopshop/queries'; -import Book from 'components/CampusInfo/svg/book.svg'; -import Cafe from 'components/CampusInfo/svg/cafe.svg'; -import Cut from 'components/CampusInfo/svg/cut.svg'; -import Flatware from 'components/CampusInfo/svg/flatware.svg'; -import Glasses from 'components/CampusInfo/svg/glasses.svg'; -import Laundry from 'components/CampusInfo/svg/laundry.svg'; -import PostOffice from 'components/CampusInfo/svg/post-office.svg'; -import Print from 'components/CampusInfo/svg/print.svg'; -import styles from './CampusInfo.module.scss'; - -const CAFETERIA_HEAD_TABLE = { - row: ['평일', '주말'], - col: ['아침', '점심', '저녁'], -}; - -const SHOP_ICON = { - 서점: , - 대즐: , - 미용실: , - 세탁소: , - 우체국: , - '복지관 참빛관 편의점': , - 복사실: , - 학생식당: , - 복지관식당: , - 오락실: , - 안경원: , - 우편취급국: , -}; - -const formatDateRange = (fromDate: string, toDate: string) => { - const from = new Date(fromDate); - const to = new Date(toDate); - - const options: Intl.DateTimeFormatOptions = { - year: 'numeric', - month: 'long', - day: 'numeric', - }; - - const fromFormatted = from.toLocaleDateString('ko-KR', options); - let toFormatted = to.toLocaleDateString('ko-KR', options); - - if (from.getFullYear() === to.getFullYear()) { - const toOptions: Intl.DateTimeFormatOptions = { - month: 'long', - day: 'numeric', - }; - - toFormatted = to.toLocaleDateString('ko-KR', toOptions); - } - - return `기간 : ${fromFormatted} - ${toFormatted}`; -}; - -function CampusInfo() { - const { data: campusInfo } = useSuspenseQuery(coopshopQueries.allShopInfo()); - - const cafeteriaInfo = campusInfo?.coop_shops.find((shop) => shop.name === '학생식당'); - const filteredCampusInfo = campusInfo?.coop_shops.filter((shop) => shop.name !== '학생식당'); - - const getFormattedShopTime = (open: string, close: string) => { - if (open === close) { - return open; - } - - return `${open} - ${close}`; - }; - - const getTimeToTypeAndDay = (type: string, day: string) => { - const target = cafeteriaInfo?.opens?.find((open) => open.day_of_week === day && open.type === type); - - return target ? getFormattedShopTime(target.open_time, target.close_time) : '미운영'; - }; - - return ( -
-
-

{`${campusInfo.semester} 시설물 운영 시간`}

-
{formatDateRange(campusInfo.from_date, campusInfo.to_date)}
-
-
-
-
-
-
-
- -
-
학생식당
-
-
- - - - {CAFETERIA_HEAD_TABLE.row.map((type) => ( - - ))} - - - - {CAFETERIA_HEAD_TABLE.col.map((type) => ( - - - {CAFETERIA_HEAD_TABLE.row.map((day) => ( - - ))} - - ))} - -
시간 - {type} -
{type} - {getTimeToTypeAndDay(type, day)} -
-
-
- - {filteredCampusInfo.slice(0, 2).map(({ id, name, opens }) => ( -
-
{SHOP_ICON[name as keyof typeof SHOP_ICON]}
-
-
{name}
- {opens.map(({ day_of_week, open_time, close_time }) => ( -
{`${day_of_week}: ${getFormattedShopTime(open_time, close_time)}`}
- ))} -
-
- ))} - -
- {filteredCampusInfo.slice(2, 6).map(({ id, name, opens }) => ( -
-
{SHOP_ICON[name as keyof typeof SHOP_ICON]}
-
-
{name}
- {opens.map(({ day_of_week, open_time, close_time }) => ( -
{`${day_of_week}: ${getFormattedShopTime(open_time, close_time)}`}
- ))} -
-
- ))} -
-
- {filteredCampusInfo.slice(6).map(({ id, name, opens }) => ( -
-
{SHOP_ICON[name as keyof typeof SHOP_ICON]}
-
-
{name}
- {opens.map(({ day_of_week, open_time, close_time }) => ( -
{`${day_of_week}: ${getFormattedShopTime(open_time, close_time)}`}
- ))} -
-
- ))} -
- - - ); -} - -export default CampusInfo; +export { default } from 'components/CampusInfo'; diff --git a/src/pages/room/[id]/index.tsx b/src/pages/room/[id]/index.tsx index ed15734a8..225656b65 100644 --- a/src/pages/room/[id]/index.tsx +++ b/src/pages/room/[id]/index.tsx @@ -20,7 +20,7 @@ import { import styles from './RoomDetailPage.module.scss'; interface RoomDetailPageProps { - id: string; + readonly id: string; } export const getStaticPaths: GetStaticPaths = async () => { diff --git a/src/pages/timetable/index.tsx b/src/pages/timetable/index.tsx index 5c6323ffd..e7eac02e3 100644 --- a/src/pages/timetable/index.tsx +++ b/src/pages/timetable/index.tsx @@ -30,6 +30,43 @@ const MobilePage = dynamic( { ssr: true }, ); +async function prefetchTimetableData( + queryClient: QueryClient, + context: GetServerSidePropsContext, + token: string, + year: number, + term: Term, + validatedFrameId: number | null, +): Promise { + try { + const mySemesterData = await queryClient.fetchQuery(timetableQueries.mySemester(token)); + const userSemester = mySemesterData?.semesters?.[0]; + const semester = year && term ? { year, term } : userSemester || getRecentSemester(); + + const timetableFrameList = await queryClient.fetchQuery(timetableQueries.frameList(token, semester)); + + const mainFrame = timetableFrameList.find((frame) => frame.is_main); + const currentFrameId = validatedFrameId ?? mainFrame?.id ?? null; + + const prefetchPromises = [ + queryClient.prefetchQuery(timetableQueries.semesterInfo()), + queryClient.prefetchQuery(deptQueries.list()), + ]; + + if (currentFrameId !== null) { + prefetchPromises.push(queryClient.prefetchQuery(timetableQueries.lectureInfo(token, currentFrameId))); + } + + await Promise.all(prefetchPromises); + } catch (error) { + const isAuthError = isServerAuthError(error); + const isForbiddenError = isKoinError(error) && error.status === 403; + if (!isAuthError && !isForbiddenError) throw error; + if (isAuthError) clearServerAuthCookies(context); + queryClient.setQueryData(timetableQueryKeys.frameList(getRecentSemester()), createDefaultTimetableFrameList()); + } +} + export const getServerSideProps = withCacheControl(async (context: GetServerSidePropsContext, cacheControl) => { const queryClient = new QueryClient(); const { token, query } = parseServerSideParams(context); @@ -39,38 +76,9 @@ export const getServerSideProps = withCacheControl(async (context: GetServerSide const validatedFrameId = isValidTimetableFrameId(frameId) ? frameId : null; if (token) { - try { - const mySemesterData = await queryClient.fetchQuery(timetableQueries.mySemester(token)); - const userSemester = mySemesterData?.semesters?.[0]; - const semester = year && term ? { year, term } : userSemester || getRecentSemester(); - - const timetableFrameList = await queryClient.fetchQuery(timetableQueries.frameList(token, semester)); - - const mainFrame = timetableFrameList.find((frame) => frame.is_main); - const currentFrameId = validatedFrameId ?? mainFrame?.id ?? null; - - const prefetchPromises = [ - queryClient.prefetchQuery(timetableQueries.semesterInfo()), - queryClient.prefetchQuery(deptQueries.list()), - ]; - - if (currentFrameId !== null) { - prefetchPromises.push(queryClient.prefetchQuery(timetableQueries.lectureInfo(token, currentFrameId))); - } - - await Promise.all(prefetchPromises); - } catch (error) { - if (!isServerAuthError(error) && !(isKoinError(error) && error.status === 403)) throw error; - if (isServerAuthError(error)) clearServerAuthCookies(context); - const semester = getRecentSemester(); - queryClient.setQueryData(timetableQueryKeys.frameList(semester), createDefaultTimetableFrameList()); - } + await prefetchTimetableData(queryClient, context, token, year, term, validatedFrameId); } else { - const semester = getRecentSemester(); - queryClient.setQueryData(timetableQueryKeys.frameList(semester), createDefaultTimetableFrameList()); - } - - if (!token) { + queryClient.setQueryData(timetableQueryKeys.frameList(getRecentSemester()), createDefaultTimetableFrameList()); cacheControl.enablePublicCache(); } diff --git a/src/utils/ts/withCacheControl.ts b/src/utils/ts/withCacheControl.ts index 41c969123..c90b6a1db 100644 --- a/src/utils/ts/withCacheControl.ts +++ b/src/utils/ts/withCacheControl.ts @@ -4,7 +4,7 @@ import type { GetServerSidePropsResult, PreviewData, } from 'next'; -import type { ParsedUrlQuery } from 'querystring'; +import type { ParsedUrlQuery } from 'node:querystring'; export const PUBLIC_SSR_CACHE_CONTROL = 'public, s-maxage=60, stale-while-revalidate=300'; export const STORE_PUBLIC_SSR_CACHE_CONTROL = 'public, s-maxage=300, stale-while-revalidate=1800'; @@ -16,26 +16,22 @@ export interface SSRCacheControl { enablePublicCache: (cacheControl?: string) => void; } -export interface GetServerSidePropsWithCacheControl< +export type GetServerSidePropsWithCacheControl< Props extends SSRPageProps = SSRPageProps, Params extends ParsedUrlQuery = ParsedUrlQuery, Preview extends PreviewData = PreviewData, -> { - ( - context: GetServerSidePropsContext, - cacheControl: SSRCacheControl, - ): Promise>; -} +> = ( + context: GetServerSidePropsContext, + cacheControl: SSRCacheControl, +) => Promise>; -export interface WithCacheControl { - < - Props extends SSRPageProps = SSRPageProps, - Params extends ParsedUrlQuery = ParsedUrlQuery, - Preview extends PreviewData = PreviewData, - >( - getServerSideProps: GetServerSidePropsWithCacheControl, - ): GetServerSideProps; -} +export type WithCacheControl = < + Props extends SSRPageProps = SSRPageProps, + Params extends ParsedUrlQuery = ParsedUrlQuery, + Preview extends PreviewData = PreviewData, +>( + getServerSideProps: GetServerSidePropsWithCacheControl, +) => GetServerSideProps; export const withCacheControl: WithCacheControl = (getServerSideProps) => async (context) => { let shouldCachePublicResponse = false; diff --git a/yarn.lock b/yarn.lock index dd76946b1..8ae15ed1d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1937,11 +1937,9 @@ __metadata: linkType: hard "@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.9.2": - version: 7.23.9 - resolution: "@babel/runtime@npm:7.23.9" - dependencies: - regenerator-runtime: "npm:^0.14.0" - checksum: 10/9a520fe1bf72249f7dd60ff726434251858de15cccfca7aa831bd19d0d3fb17702e116ead82724659b8da3844977e5e13de2bae01eb8a798f2823a669f122be6 + version: 7.29.2 + resolution: "@babel/runtime@npm:7.29.2" + checksum: 10/f55ba4052aa0255055b34371a145fbe69c29b37b49eaea14805b095bfb4153701486416e89392fd27ec8abafa53868be86e960b9f8f959fff91f2c8ac2a14b02 languageName: node linkType: hard @@ -3157,10 +3155,10 @@ __metadata: languageName: node linkType: hard -"@next/env@npm:15.5.15": - version: 15.5.15 - resolution: "@next/env@npm:15.5.15" - checksum: 10/563340390668c717ca179067c3caf7f9e12b282b66a05a5f6b3dec1ab64cfdd93e08d7f2ac4fdb37fffcb8c32911054a35e6c9d3a443fba990c2a0c868db0514 +"@next/env@npm:15.5.16": + version: 15.5.16 + resolution: "@next/env@npm:15.5.16" + checksum: 10/4ba5975b7b4e887eec3b15e68fa30b687412bde3c7167c82023f054b66b53932ff9cb18af454b8b380be8a51a0eb04700635be52ecaa42816bfd12314c11ad01 languageName: node linkType: hard @@ -3173,58 +3171,58 @@ __metadata: languageName: node linkType: hard -"@next/swc-darwin-arm64@npm:15.5.15": - version: 15.5.15 - resolution: "@next/swc-darwin-arm64@npm:15.5.15" +"@next/swc-darwin-arm64@npm:15.5.16": + version: 15.5.16 + resolution: "@next/swc-darwin-arm64@npm:15.5.16" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@next/swc-darwin-x64@npm:15.5.15": - version: 15.5.15 - resolution: "@next/swc-darwin-x64@npm:15.5.15" +"@next/swc-darwin-x64@npm:15.5.16": + version: 15.5.16 + resolution: "@next/swc-darwin-x64@npm:15.5.16" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@next/swc-linux-arm64-gnu@npm:15.5.15": - version: 15.5.15 - resolution: "@next/swc-linux-arm64-gnu@npm:15.5.15" +"@next/swc-linux-arm64-gnu@npm:15.5.16": + version: 15.5.16 + resolution: "@next/swc-linux-arm64-gnu@npm:15.5.16" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@next/swc-linux-arm64-musl@npm:15.5.15": - version: 15.5.15 - resolution: "@next/swc-linux-arm64-musl@npm:15.5.15" +"@next/swc-linux-arm64-musl@npm:15.5.16": + version: 15.5.16 + resolution: "@next/swc-linux-arm64-musl@npm:15.5.16" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@next/swc-linux-x64-gnu@npm:15.5.15": - version: 15.5.15 - resolution: "@next/swc-linux-x64-gnu@npm:15.5.15" +"@next/swc-linux-x64-gnu@npm:15.5.16": + version: 15.5.16 + resolution: "@next/swc-linux-x64-gnu@npm:15.5.16" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@next/swc-linux-x64-musl@npm:15.5.15": - version: 15.5.15 - resolution: "@next/swc-linux-x64-musl@npm:15.5.15" +"@next/swc-linux-x64-musl@npm:15.5.16": + version: 15.5.16 + resolution: "@next/swc-linux-x64-musl@npm:15.5.16" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@next/swc-win32-arm64-msvc@npm:15.5.15": - version: 15.5.15 - resolution: "@next/swc-win32-arm64-msvc@npm:15.5.15" +"@next/swc-win32-arm64-msvc@npm:15.5.16": + version: 15.5.16 + resolution: "@next/swc-win32-arm64-msvc@npm:15.5.16" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@next/swc-win32-x64-msvc@npm:15.5.15": - version: 15.5.15 - resolution: "@next/swc-win32-x64-msvc@npm:15.5.15" +"@next/swc-win32-x64-msvc@npm:15.5.16": + version: 15.5.16 + resolution: "@next/swc-win32-x64-msvc@npm:15.5.16" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -4661,13 +4659,6 @@ __metadata: languageName: node linkType: hard -"@trysound/sax@npm:0.2.0": - version: 0.2.0 - resolution: "@trysound/sax@npm:0.2.0" - checksum: 10/7379713eca480ac0d9b6c7b063e06b00a7eac57092354556c81027066eb65b61ea141a69d0cc2e15d32e05b2834d4c9c2184793a5e36bbf5daf05ee5676af18c - languageName: node - linkType: hard - "@tybys/wasm-util@npm:^0.10.0": version: 0.10.1 resolution: "@tybys/wasm-util@npm:0.10.1" @@ -4986,12 +4977,12 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:^19.2.10": - version: 19.2.10 - resolution: "@types/react@npm:19.2.10" +"@types/react@npm:^19.2.14": + version: 19.2.14 + resolution: "@types/react@npm:19.2.14" dependencies: csstype: "npm:^3.2.2" - checksum: 10/0e34a0e42db02f4b3f4bed446128c555446c2854b833d71d597e6c8b08800dd90519a03a226ad250cccc4a0c9e51bd3f9c54cdcb79ee1219f4d5b318fb3a3813 + checksum: 10/fbff239089ee64b6bd9b00543594db498278b06de527ef1b0f71bb0eb09cc4445a71b5dd3c0d3d0257255c4eed94406be40a74ad4a987ade8a8d5dd65c82bc5f languageName: node linkType: hard @@ -5749,14 +5740,14 @@ __metadata: languageName: node linkType: hard -"axios@npm:^0.31.0": - version: 0.31.0 - resolution: "axios@npm:0.31.0" +"axios@npm:^0.31.1": + version: 0.31.1 + resolution: "axios@npm:0.31.1" dependencies: follow-redirects: "npm:^1.15.4" form-data: "npm:^4.0.4" proxy-from-env: "npm:^1.1.0" - checksum: 10/80536a915c36ff9e31f32a25e93f558963a89b4182b90a829c0fab44d5c9ee84ebbe34501c45448f422a181db75b8bab4272076a6b128184da2bfa340943d0d5 + checksum: 10/097dffdc0ab1229fd2f2e792d038e81272568e8280217933f4ad68234761ffebe2b51468f6190f5d98eb3df9511800083f74f78dda4c6229b24b4f9d98d300e5 languageName: node linkType: hard @@ -5925,12 +5916,12 @@ __metadata: linkType: hard "brace-expansion@npm:^1.1.7": - version: 1.1.11 - resolution: "brace-expansion@npm:1.1.11" + version: 1.1.14 + resolution: "brace-expansion@npm:1.1.14" dependencies: balanced-match: "npm:^1.0.0" concat-map: "npm:0.0.1" - checksum: 10/faf34a7bb0c3fcf4b59c7808bc5d2a96a40988addf2e7e09dfbb67a2251800e0d14cd2bfc1aa79174f2f5095c54ff27f46fb1289fe2d77dac755b5eb3434cc07 + checksum: 10/2de747a5891ea0d3a1946ea1ae26e056a47f7ea8d42a3009e1736ec3a31a5aa69a3c5da59d998426773553afe4c258e5b12d7953b534fa7f2cf12ce92eed4931 languageName: node linkType: hard @@ -5953,11 +5944,11 @@ __metadata: linkType: hard "braces@npm:^3.0.2, braces@npm:~3.0.2": - version: 3.0.2 - resolution: "braces@npm:3.0.2" + version: 3.0.3 + resolution: "braces@npm:3.0.3" dependencies: - fill-range: "npm:^7.0.1" - checksum: 10/966b1fb48d193b9d155f810e5efd1790962f2c4e0829f8440b8ad236ba009222c501f70185ef732fef17a4c490bb33a03b90dab0631feafbdf447da91e8165b1 + fill-range: "npm:^7.1.1" + checksum: 10/fad11a0d4697a27162840b02b1fad249c1683cbc510cd5bf1a471f2f8085c046d41094308c577a50a03a579dd99d5a6b3724c4b5e8b14df2c4443844cfcda2c6 languageName: node linkType: hard @@ -7888,12 +7879,12 @@ __metadata: languageName: node linkType: hard -"fill-range@npm:^7.0.1": - version: 7.0.1 - resolution: "fill-range@npm:7.0.1" +"fill-range@npm:^7.1.1": + version: 7.1.1 + resolution: "fill-range@npm:7.1.1" dependencies: to-regex-range: "npm:^5.0.1" - checksum: 10/e260f7592fd196b4421504d3597cc76f4a1ca7a9488260d533b611fc3cefd61e9a9be1417cb82d3b01ad9f9c0ff2dbf258e1026d2445e26b0cf5148ff4250429 + checksum: 10/a7095cb39e5bc32fada2aa7c7249d3f6b01bd1ce461a61b0adabacccabd9198500c6fb1f68a7c851a657e273fce2233ba869638897f3d7ed2e87a2d89b4436ea languageName: node linkType: hard @@ -7940,9 +7931,9 @@ __metadata: linkType: hard "flatted@npm:^3.2.9": - version: 3.2.9 - resolution: "flatted@npm:3.2.9" - checksum: 10/dc2b89e46a2ebde487199de5a4fcb79e8c46f984043fea5c41dbf4661eb881fefac1c939b5bdcd8a09d7f960ec364f516970c7ec44e58ff451239c07fd3d419b + version: 3.4.2 + resolution: "flatted@npm:3.4.2" + checksum: 10/a9e78fe5c2c1fcd98209a015ccee3a6caa953e01729778e83c1fe92e68601a63e1e69cd4e573010ca99eaf585a581b80ccf1018b99283e6cbc2117bcba1e030f languageName: node linkType: hard @@ -8672,9 +8663,9 @@ __metadata: linkType: hard "immutable@npm:^4.0.0": - version: 4.3.5 - resolution: "immutable@npm:4.3.5" - checksum: 10/dbc1b8c808b9aa18bfce2e0c7bc23714a47267bc311f082145cc9220b2005e9b9cd2ae78330f164a19266a2b0f78846c60f4f74893853ac16fd68b5ae57092d2 + version: 4.3.8 + resolution: "immutable@npm:4.3.8" + checksum: 10/27a134cec0089ba60c5f50fdd08a0296533c9ce6d112188461c89a6bb3c720368e4363dc5f8d40440c6f9120400867e9cf86bd7463c7d3ee12d4ad44f797d4cc languageName: node linkType: hard @@ -9867,14 +9858,14 @@ __metadata: linkType: hard "js-yaml@npm:^3.13.1": - version: 3.14.1 - resolution: "js-yaml@npm:3.14.1" + version: 3.14.2 + resolution: "js-yaml@npm:3.14.2" dependencies: argparse: "npm:^1.0.7" esprima: "npm:^4.0.0" bin: js-yaml: bin/js-yaml.js - checksum: 10/9e22d80b4d0105b9899135365f746d47466ed53ef4223c529b3c0f7a39907743fdbd3c4379f94f1106f02755b5e90b2faaf84801a891135544e1ea475d1a1379 + checksum: 10/172e0b6007b0bf0fc8d2469c94424f7dd765c64a047d2b790831fecef2204a4054eabf4d911eb73ab8c9a3256ab8ba1ee8d655b789bf24bf059c772acc2075a1 languageName: node linkType: hard @@ -10032,10 +10023,10 @@ __metadata: "@types/jest": "npm:^27.0.1" "@types/navermaps": "npm:^3.6.4" "@types/node": "npm:^16.7.13" - "@types/react": "npm:^19.2.10" + "@types/react": "npm:^19.2.14" "@types/react-dom": "npm:^19.2.3" "@types/react-window": "npm:^1.8.5" - axios: "npm:^0.31.0" + axios: "npm:^0.31.1" dayjs: "npm:^1.11.12" dotenv: "npm:^17.2.3" embla-carousel-autoplay: "npm:^8.0.4" @@ -10056,11 +10047,11 @@ __metadata: idb: "npm:^8.0.3" jest: "npm:^29.7.0" lottie-react: "npm:^2.4.1" - next: "npm:15.5.15" - postcss: "npm:^8.4.47" + next: "npm:15.5.16" + postcss: "npm:^8.5.11" prettier: "npm:^3.6.2" - react: "npm:^19.2.5" - react-dom: "npm:^19.2.5" + react: "npm:^19.2.6" + react-dom: "npm:^19.2.6" react-hook-form: "npm:^7.55.0" react-quill: "npm:^2.0.0" react-quill-new: "npm:^3.6.0" @@ -10140,9 +10131,9 @@ __metadata: linkType: hard "lodash-es@npm:^4.17.21": - version: 4.17.21 - resolution: "lodash-es@npm:4.17.21" - checksum: 10/03f39878ea1e42b3199bd3f478150ab723f93cc8730ad86fec1f2804f4a07c6e30deaac73cad53a88e9c3db33348bb8ceeb274552390e7a75d7849021c02df43 + version: 4.18.1 + resolution: "lodash-es@npm:4.18.1" + checksum: 10/8bfad225ef09ef42b04283cdaf7830efcc2ba29ae41b56501c74422155ee1ccaa1f0f6e8319def3451a1fe54dec501c8e4bee622bae2b2d98ac993731e0a5cce languageName: node linkType: hard @@ -10182,9 +10173,9 @@ __metadata: linkType: hard "lodash@npm:>=4.17.21, lodash@npm:^4.17.15, lodash@npm:^4.17.4": - version: 4.17.21 - resolution: "lodash@npm:4.17.21" - checksum: 10/c08619c038846ea6ac754abd6dd29d2568aa705feb69339e836dfa8d8b09abbb2f859371e86863eda41848221f9af43714491467b5b0299122431e202bb0c532 + version: 4.18.1 + resolution: "lodash@npm:4.18.1" + checksum: 10/306fea53dfd39dad1f03d45ba654a2405aebd35797b673077f401edb7df2543623dc44b9effbb98f69b32152295fff725a4cec99c684098947430600c6af0c3f languageName: node linkType: hard @@ -10454,24 +10445,15 @@ __metadata: linkType: hard "minimatch@npm:^3.0.3, minimatch@npm:^3.0.4, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": - version: 3.1.2 - resolution: "minimatch@npm:3.1.2" + version: 3.1.5 + resolution: "minimatch@npm:3.1.5" dependencies: brace-expansion: "npm:^1.1.7" - checksum: 10/e0b25b04cd4ec6732830344e5739b13f8690f8a012d73445a4a19fbc623f5dd481ef7a5827fde25954cd6026fede7574cc54dc4643c99d6c6b653d6203f94634 - languageName: node - linkType: hard - -"minimatch@npm:^9.0.1": - version: 9.0.3 - resolution: "minimatch@npm:9.0.3" - dependencies: - brace-expansion: "npm:^2.0.1" - checksum: 10/c81b47d28153e77521877649f4bab48348d10938df9e8147a58111fe00ef89559a2938de9f6632910c4f7bf7bb5cd81191a546167e58d357f0cfb1e18cecc1c5 + checksum: 10/b11a7ee5773cd34c1a0c8436cdbe910901018fb4b6cb47aa508a18d567f6efd2148507959e35fba798389b161b8604a2d704ccef751ea36bd4582f9852b7d63f languageName: node linkType: hard -"minimatch@npm:^9.0.4": +"minimatch@npm:^9.0.1, minimatch@npm:^9.0.4": version: 9.0.5 resolution: "minimatch@npm:9.0.5" dependencies: @@ -10635,7 +10617,7 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:^3.3.6": +"nanoid@npm:^3.3.11, nanoid@npm:^3.3.6": version: 3.3.11 resolution: "nanoid@npm:3.3.11" bin: @@ -10676,19 +10658,19 @@ __metadata: languageName: node linkType: hard -"next@npm:15.5.15": - version: 15.5.15 - resolution: "next@npm:15.5.15" +"next@npm:15.5.16": + version: 15.5.16 + resolution: "next@npm:15.5.16" dependencies: - "@next/env": "npm:15.5.15" - "@next/swc-darwin-arm64": "npm:15.5.15" - "@next/swc-darwin-x64": "npm:15.5.15" - "@next/swc-linux-arm64-gnu": "npm:15.5.15" - "@next/swc-linux-arm64-musl": "npm:15.5.15" - "@next/swc-linux-x64-gnu": "npm:15.5.15" - "@next/swc-linux-x64-musl": "npm:15.5.15" - "@next/swc-win32-arm64-msvc": "npm:15.5.15" - "@next/swc-win32-x64-msvc": "npm:15.5.15" + "@next/env": "npm:15.5.16" + "@next/swc-darwin-arm64": "npm:15.5.16" + "@next/swc-darwin-x64": "npm:15.5.16" + "@next/swc-linux-arm64-gnu": "npm:15.5.16" + "@next/swc-linux-arm64-musl": "npm:15.5.16" + "@next/swc-linux-x64-gnu": "npm:15.5.16" + "@next/swc-linux-x64-musl": "npm:15.5.16" + "@next/swc-win32-arm64-msvc": "npm:15.5.16" + "@next/swc-win32-x64-msvc": "npm:15.5.16" "@swc/helpers": "npm:0.5.15" caniuse-lite: "npm:^1.0.30001579" postcss: "npm:8.4.31" @@ -10731,7 +10713,7 @@ __metadata: optional: true bin: next: dist/bin/next - checksum: 10/f15867d9e068194376a9657a13b5bc28dee4afedf4a163304c49cf9cec008ad5b905b8f4639bbebd702f77342b76847252b02dffeb130dd07be24aa0d2a694f0 + checksum: 10/6575ffe4b6a1f76e48c689064296aa95326ec8494f4dc9baec8918e1e6af88ac16f1a5d0dd4c4caa5d833350ffeadfd94141d7f27d76099da0312394f21e7b85 languageName: node linkType: hard @@ -11208,7 +11190,7 @@ __metadata: languageName: node linkType: hard -"picocolors@npm:^1.0.1, picocolors@npm:^1.1.0": +"picocolors@npm:^1.0.1": version: 1.1.0 resolution: "picocolors@npm:1.1.0" checksum: 10/a2ad60d94d185c30f2a140b19c512547713fb89b920d32cc6cf658fa786d63a37ba7b8451872c3d9fc34883971fb6e5878e07a20b60506e0bb2554dce9169ccb @@ -11223,9 +11205,9 @@ __metadata: linkType: hard "picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.2.3, picomatch@npm:^2.3.1": - version: 2.3.1 - resolution: "picomatch@npm:2.3.1" - checksum: 10/60c2595003b05e4535394d1da94850f5372c9427ca4413b71210f437f7b2ca091dbd611c45e8b37d10036fa8eade25c1b8951654f9d3973bfa66a2ff4d3b08bc + version: 2.3.2 + resolution: "picomatch@npm:2.3.2" + checksum: 10/b788ef8148a2415b9dec12f0bb350ae6a5830f8f1950e472abc2f5225494debf7d1b75eb031df0ceaea9e8ec3e7bad599e8dbf3c60d61b42be429ba41bff4426 languageName: node linkType: hard @@ -11360,14 +11342,14 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.4.47": - version: 8.4.47 - resolution: "postcss@npm:8.4.47" +"postcss@npm:^8.5.11": + version: 8.5.14 + resolution: "postcss@npm:8.5.14" dependencies: - nanoid: "npm:^3.3.7" - picocolors: "npm:^1.1.0" + nanoid: "npm:^3.3.11" + picocolors: "npm:^1.1.1" source-map-js: "npm:^1.2.1" - checksum: 10/f2b50ba9b6fcb795232b6bb20de7cdc538c0025989a8ed9c4438d1960196ba3b7eaff41fdb1a5c701b3504651ea87aeb685577707f0ae4d6ce6f3eae5df79a81 + checksum: 10/2e3f4dea69692918fe9df5402beb0e54df84499995a094f2fbf63d1a9e38bc1b7a42854df47f09e02593213e01a5eb0627b1d1bd6d1b0ea90767b2e072f7167c languageName: node linkType: hard @@ -11576,14 +11558,14 @@ __metadata: languageName: node linkType: hard -"react-dom@npm:^19.2.5": - version: 19.2.5 - resolution: "react-dom@npm:19.2.5" +"react-dom@npm:^19.2.6": + version: 19.2.6 + resolution: "react-dom@npm:19.2.6" dependencies: scheduler: "npm:^0.27.0" peerDependencies: - react: ^19.2.5 - checksum: 10/ba14b022c7191d27b723314b964a1a4d839832e6edd231294097e323973808f97ac647bcf182ab0104e20ae6532dbc36733aec3e8333a1446a7183099c96b255 + react: ^19.2.6 + checksum: 10/695122e37dec3460311231ff1f9a96368d74457d4ccf60010eaebc08c3e1c47b054c9267b40d98c689ae99d0c29a340acacfd405016a954a4f11613146191e68 languageName: node linkType: hard @@ -11686,10 +11668,10 @@ __metadata: languageName: node linkType: hard -"react@npm:^19.2.5": - version: 19.2.5 - resolution: "react@npm:19.2.5" - checksum: 10/1c3c7ffecb90b7f89a5c3ef635e6811f3a84600097f203b918150cb7e6b0a52915e858e5b4c82317a520dffccfa46ee4819ccf92c59c5b2d6c25cffe258dd20c +"react@npm:^19.2.6": + version: 19.2.6 + resolution: "react@npm:19.2.6" + checksum: 10/205f0db93bd6c6485f94471d1c3437ae1d410f322297c76186fe4f658f88a6786bb0805e3cf8f330f24d1daa923b6b610ec63960d2461e9201bca64ca8361db9 languageName: node linkType: hard @@ -11767,13 +11749,6 @@ __metadata: languageName: node linkType: hard -"regenerator-runtime@npm:^0.14.0": - version: 0.14.1 - resolution: "regenerator-runtime@npm:0.14.1" - checksum: 10/5db3161abb311eef8c45bcf6565f4f378f785900ed3945acf740a9888c792f75b98ecb77f0775f3bf95502ff423529d23e94f41d80c8256e8fa05ed4b07cf471 - languageName: node - linkType: hard - "regexp.prototype.flags@npm:^1.5.1": version: 1.5.1 resolution: "regexp.prototype.flags@npm:1.5.1" @@ -12189,6 +12164,13 @@ __metadata: languageName: node linkType: hard +"sax@npm:^1.5.0": + version: 1.6.0 + resolution: "sax@npm:1.6.0" + checksum: 10/0909cedcd9f011ceeac80c0240a92d64ef712cf6c04e0f6ee236a8d812f86a59f61bee6bb5da28d75306db050b99e0593051ea77351795822253b984af6cf044 + languageName: node + linkType: hard + "scheduler@npm:^0.27.0": version: 0.27.0 resolution: "scheduler@npm:0.27.0" @@ -13176,19 +13158,19 @@ __metadata: linkType: hard "svgo@npm:^3.0.2": - version: 3.3.2 - resolution: "svgo@npm:3.3.2" + version: 3.3.3 + resolution: "svgo@npm:3.3.3" dependencies: - "@trysound/sax": "npm:0.2.0" commander: "npm:^7.2.0" css-select: "npm:^5.1.0" css-tree: "npm:^2.3.1" css-what: "npm:^6.1.0" csso: "npm:^5.0.5" picocolors: "npm:^1.0.0" + sax: "npm:^1.5.0" bin: svgo: ./bin/svgo - checksum: 10/82fdea9b938884d808506104228e4d3af0050d643d5b46ff7abc903ff47a91bbf6561373394868aaf07a28f006c4057b8fbf14bbd666298abdd7cc590d4f7700 + checksum: 10/f3c1b4d05d1704483e53515d5995af5f06a2718df85e3a8320f57bb256b8dc926b84c87a1a9b98e9d3ca1224314cc0676a803bdd03163508292f2d45c7077096 languageName: node linkType: hard @@ -13215,8 +13197,8 @@ __metadata: linkType: hard "tar@npm:^6.1.11, tar@npm:^6.1.2": - version: 6.2.0 - resolution: "tar@npm:6.2.0" + version: 6.2.1 + resolution: "tar@npm:6.2.1" dependencies: chownr: "npm:^2.0.0" fs-minipass: "npm:^2.0.0" @@ -13224,7 +13206,7 @@ __metadata: minizlib: "npm:^2.1.1" mkdirp: "npm:^1.0.3" yallist: "npm:^4.0.0" - checksum: 10/2042bbb14830b5cd0d584007db0eb0a7e933e66d1397e72a4293768d2332449bc3e312c266a0887ec20156dea388d8965e53b4fc5097f42d78593549016da089 + checksum: 10/bfbfbb2861888077fc1130b84029cdc2721efb93d1d1fb80f22a7ac3a98ec6f8972f29e564103bbebf5e97be67ebc356d37fa48dbc4960600a1eb7230fbd1ea0 languageName: node linkType: hard