diff --git a/.eslintrc.js b/.eslintrc.js index a68365a..23bdd37 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -27,12 +27,14 @@ module.exports = { 'filenames', // 파일이름 강제 ], rules: { + //@storybook/react 빨간줄 오류 삭제 + 'storybook/no-renderer-packages': 'off', // 절대경로 사용 'no-relative-import-paths/no-relative-import-paths': [ 'error', { allowSameFolder: true, rootDir: 'src', prefix: '@' }, ], - + 'prettier/prettier': 'off', // Typescript '@typescript-eslint/no-explicit-any': 'error', // any 사용 금지 '@typescript-eslint/prefer-nullish-coalescing': 'warn', // ?? 연산자 사용 권장 @@ -49,49 +51,8 @@ module.exports = { // 개발 편의를 위한 완화 '@typescript-eslint/explicit-function-return-type': 'off', // 함수 반환 타입 명시 선택사항 'react/no-unescaped-entities': 'off', // JSX 안에 특수문자 직접 사용 허용 - // 네이밍 컨벤션 - '@typescript-eslint/naming-convention': [ - 'error', - { - // 변수 & 함수 - selector: 'variableLike', - format: ['camelCase'], - }, - { - // 상수 - selector: 'variable', - modifiers: ['const'], - format: ['UPPER_CASE'], - }, - { - // 타입, 인터페이스, 클래스 - selector: 'typeLike', - format: ['PascalCase'], - }, - { - // enum - selector: 'enum', - format: ['PascalCase'], - }, - ], }, overrides: [ - { - // config ,pages, stories 컨벤션 완화 - files: [ - 'src/pages/**/*.tsx', - 'src/stories/**/*.tsx', - 'src/stories/**/*.ts', - '*.config.ts', - '*.config.js', - '*.config.mjs', - '*.json', - '*.d.ts', - ], - rules: { - '@typescript-eslint/naming-convention': 'off', - }, - }, { files: ['*.config.ts', '*.config.js', '*.config.mjs', '*.json', '*.d.ts'], rules: { @@ -100,7 +61,10 @@ module.exports = { }, ], settings: { - react: { version: 'detect' }, // react 설치 후 버전 명시 + react: { version: '18.3.1' }, + 'import/resolver': { + typescript: { project: './tsconfig.json' }, + }, }, ignorePatterns: [ 'node_modules/', diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 303012f..fa24ec2 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -6,15 +6,15 @@ labels: bug assignees: [] --- -## 🐛 버그 설명 +## 🐛 버그 설명 (필수) -## ⚡ 재현 방법 +## ⚡ 재현 방법 (필수) -## ✅ 기대 동작 +## ✅ 기대 동작 (필수) @@ -26,4 +26,4 @@ assignees: [] -## 📸 스크린샷 (선택) +## 📸 스크린샷 diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 6982bf5..9577c74 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -6,7 +6,7 @@ labels: feature assignees: [] --- -## ✨ 기능 설명 +## ✨ 기능 설명 (필수) @@ -17,6 +17,6 @@ assignees: [] - [ ] 작업 1 - [ ] 작업 2 -## 🔗 참고 자료 (선택) +## 🔗 참고 자료 diff --git a/.github/ISSUE_TEMPLATE/qa_template.md b/.github/ISSUE_TEMPLATE/qa_template.md index e698f56..153689e 100644 --- a/.github/ISSUE_TEMPLATE/qa_template.md +++ b/.github/ISSUE_TEMPLATE/qa_template.md @@ -6,7 +6,7 @@ labels: ["qa", "testing"] assignees: [] --- -## ✅ QA 체크리스트 +## ✅ QA 체크리스트 (필수) @@ -26,4 +26,4 @@ assignees: [] -## 📸 스크린샷 (선택) +## 📸 스크린샷 diff --git a/.github/auto_assign.yml b/.github/auto-assign.yml similarity index 100% rename from .github/auto_assign.yml rename to .github/auto-assign.yml diff --git a/.github/workflows/add-issues-to-project.yml b/.github/workflows/add-issues-to-project.yml new file mode 100644 index 0000000..fa16937 --- /dev/null +++ b/.github/workflows/add-issues-to-project.yml @@ -0,0 +1,15 @@ +name: Add new issues to GitHub Project + +on: + issues: + types: [opened] + +jobs: + add-to-project: + runs-on: ubuntu-latest + steps: + - name: Add issue to project + uses: actions/add-to-project@v0.5.0 + with: + project-url: https://github.com/orgs/codeit-FE18-part3/projects/6 + github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/auto_assign.yml b/.github/workflows/auto-assign.yml similarity index 100% rename from .github/workflows/auto_assign.yml rename to .github/workflows/auto-assign.yml diff --git a/.storybook/main.ts b/.storybook/main.ts index 52ee85e..4387353 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -8,7 +8,6 @@ const config: StorybookConfig = { '@storybook/addon-docs', '@storybook/addon-a11y', '@storybook/addon-themes', - '@storybook/addon-vitest', ], framework: { name: '@storybook/nextjs', diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 70f8d67..f6e241b 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -1,4 +1,4 @@ -import type { Preview } from "@storybook/nextjs-vite"; +import type { Preview } from "@storybook/nextjs"; import { withThemeByClassName } from "@storybook/addon-themes"; import "@/styles/globals.css"; import "@/styles/fonts.css"; diff --git a/.storybook/vitest.setup.ts b/.storybook/vitest.setup.ts deleted file mode 100644 index ab80cc1..0000000 --- a/.storybook/vitest.setup.ts +++ /dev/null @@ -1,6 +0,0 @@ -// vitest.setup.ts -import * as a11yAddonAnnotations from '@storybook/addon-a11y/preview'; -import { setProjectAnnotations } from '@storybook/nextjs'; -import * as projectAnnotations from './preview'; - -setProjectAnnotations([a11yAddonAnnotations, projectAnnotations]); diff --git a/README.md b/README.md index 1990c59..db47f17 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,242 @@ # Thejulge +노션 협업 문서를 바탕으로 정리한 **중급 프로젝트 README**입니다. +--- +## 📌 개요 + +- **프로젝트 기간**: 2025-09-29 ~ 2025-10-22 (제출 마감 23:50) +- **목표** + - 알바 공고와 가게/지원자를 잇는 서비스의 핵심 플로우(**회원가입 → 프로필 → 공고 등록·조회 → + 상세**) 완성 + - 공통 UI 컴포넌트(버튼/인풋/모달 등) 정리 및 **Storybook 문서화** + - **배포 파이프라인(Vercel)**과 팀 협업 파이프라인 정착 +- **핵심지표(예시)**: 기능 커버리지, QA 체크리스트 통과율, Storybook 커버리지, E2E 통과 + +--- + +## 👥 팀 & 역할 (RnR) + +| 구성원 | 공통 작업 | UI 컴포넌트 | 페이지 | + +|---|---|---|---| + +| 팀원전원 | 디자인 및 기능 QA / 본인 작업관련 문서 | | | + +| 박신천 | | 인풋, 모달, 버튼 | 로그인, 회원가입, 내프로필 등록, 상세 | + +| 양재영 | 시연영상 준비 | 공통 프레임, 푸터, 토스트, 페이지네이션, 스켈레톤 UI | 가게 정보 등록, +상세 | + +| 위소현 | 초기 프로젝트 셋팅 / 공용 문서 작성 및 관리(노션) | 헤더, 필터, 드롭다운, post, 컬러, +폰트, 아이콘 | 공고 리스트, 상세 | + +| 유인화 | 발표자료 및 발표 | 테이블, 알림, 캘린더 | 가게 공고 등록, 상세 | + +> 담당 범위는 개발 중 상호 협의로 조정될 수 있음. + +--- + +## 🧰 기술 스택 + +- **Next.js (React) + TypeScript** +- **Tailwind CSS** (공통 컬러/폰트/유틸) +- **Storybook** (컬러/폰트/아이콘 가이드) +- **ESLint · Prettier** +- **Vercel 배포** +- **GitHub Issues/Projects** · 브랜치 전략 · 템플릿 +- **Discord/GitHub 웹훅** + +> ※ “기술 스택 선정 배경” 문서는 베이스로 존재하며 상세 설명은 후속 업데이트 예정. + +--- + +## ✨ 주요 기능 + +- **회원 인증**: 회원가입, 로그인 +- **마이페이지**: 내 프로필 등록/수정, 상세 보기 +- **가게**: 가게 정보 등록, 상세 +- **공고**: 공고 리스트, 상세, 가게 공고 등록 +- **공통 UI**: 버튼, 인풋, 모달, 헤더, 필터, 드롭다운, 토스트, 페이지네이션, 스켈레톤, 테이블, 알림, + 캘린더 + +--- + +## 🗓️ 진행 일정 (요약) + +- **전체 기간**: 2025-09-29 ~ 2025-10-22 +- 1차 중간점검 → 2차 중간점검 → 테스트·수정·코드개선 → 발표 준비 → 최종 점검 및 제출 + +### ▸ 프로젝트 일정표 + +- **1. 프로젝트 주제 선정** — 2025/09/25 → 2025/09/26 + - 주제 선정, 기획 분석 및 RnR 분배 +- **2. 프로젝트 초기 셋팅 및 필요 지식 습득** — 2025/09/27 → 2025/09/28 + - Github repo 만들기, Github fork, project todo 작성 등 / 초기 셋팅 재점검(config 등) / Tailwind + 컬러·폰트 정의 후 배포 +- **3. 공통 UI 컴포넌트 제작** — 2025/09/28 → 2025/10/01 + - 버튼, 인풋, 모달 등 최소 UI → 이후 페이지 작업 가능 상태 만들기 +- **4. 1차 중간점검** — 2025/10/01 + - 2시 팀 미팅에서 공통 리소스 점검, 마감 19:00 +- **5. 기능 컴포넌트 및 페이지 제작** — 2025/10/04 → 2025/10/15 + - 퍼블리싱·API 등 스타일에 맞춰 구현 / 마감은 매일 14:00 팀 미팅 전 +- **6. 2차 중간점검** — 2025/10/16 + - 기능 및 디자인 QA, 요구사항 체크리스트 점검 +- **7. 테스트 및 수정, 코드 개선** — 2025/10/16 → 2025/10/19 + - QA에서 나온 버그/수정사항 반영 및 개선 +- **8. 프로젝트 발표 준비** — 2025/10/18 → 2025/10/19 + - PPT/영상 준비(개인 스케줄에 맞춤) → 발표 시 영상 또는 시연 +- **9. 최종점검** — 2025/10/20 + - 모든 버그 및 요구사항 체크리스트 통과 +- **10. 발표** — 2025/10/21 + - 13:00 발표 → 이후 PPT/영상 산출물 팀 공유(제출용) +- **11. 완성 및 제출** — 2025/10/22 + - 코드 작업 마감 19:00 / 결과물 제출 23:50 + +### ▸ 세부 계획 + +- 상기 일정과 동일한 마일스톤 기준으로 각 작업을 세분화하여 진행 (퍼블리싱, API 연동, 리팩토링, + 테스트/QA, 산출물 제작 등) + +--- + +## ✅ 초기 셋업 TODO 현황 + +- [O] 프로젝트 협업 문서 작성 및 업데이트 (커뮤니케이션, 컨벤션, 프로젝트 관리, 기술스택) +- [O] 기술스택 설치 + package script 작성 +- [O] ESLint 설정 +- [O] Prettier 설정 +- [O] Tailwind config 공통 설정 (컬러, 폰트) +- [O] Storybook 공통 작성 (컬러, 폰트, 아이콘) +- [O] Vercel 연동 +- [O] Discord · GitHub 웹훅 연동 +- [O] GitHub repo/브랜치/이슈 템플릿/프로젝트 추가 +- [O] 웹폰트 작성 (CDN 미제공) +- [O] 기타 기본 설정: `.gitignore` / `.editorconfig` / `.storybook` / `.vscode` +- [O] `next.config` 이미지 경로 설정 +- [O] `.env.example` 작성 +- [O] 기본 폴더 구조 생성(+ `index.ts` 포함) +- [O] 공통 아이콘 이미지 export +- [O] Tailwind util 함수 작성 + +--- + +## 🤝 협업 컨벤션 + +- **브랜치**: `main`(보호) / `develop` / `feature/*` (이슈 번호 기반 네이밍) +- **커밋**: Gitmoji/Conventional 권장, 작은 단위로 빈번하게 +- **PR**: 템플릿 사용 · 체크리스트 · 스크린샷/Storybook 링크 첨부 +- **이슈/프로젝트**: 작업 단위 이슈 → PR 연결, 라벨/담당자/마일스톤 운영 +- **QA**: 데일리 스크럼 기준으로 진행, 디자인/기능 QA는 전원 공통 참여 + +--- + +## 🗂️ 폴더 구조 (최종 컨벤션 반영) + +> Next.js는 **Pages Router** 기준이며, API 디렉터리는 **백엔드 통신 헬퍼 전용**(프론트에서 사용)으로 +> 취급합니다. + +``` +project-root/ +├── 📁 components/ # 재사용 가능한 UI 컴포넌트 +│ ├── 📁 common/ # 공통 컴포넌트 (SEO, ErrorBoundary 등) +│ ├── 📁 ui/ # 기본 UI 컴포넌트 (Button, Input 등) +│ ├── 📁 layout/ # 레이아웃 컴포넌트 (Header, footer) +│ └── 📁 features/ # 기능 컴포넌트 (Form, Post, List) +│ +├── 📁 pages/ # Next.js Pages Router +│ ├── 📁 dashboard/ # 구현할 페이지 예시 (대시보드 페이지들) +│ ├── 📁 mypage/ # 구현할 페이지 예시 (마이페이지) +│ ├── _app.ts # 글로벌 App 컴포넌트 +│ ├── _document.ts # HTML Document 커스터마이징 +│ └── index.ts # 메인페이지 +│ +├── 📁 api/ # API 관련코드 (백엔드 통신 전용, next.js는 단순 프론트로 사용) +│ ├── 📁 posts/ +│ └── 📁 users/ +│ +├── 📁 lib/ # 유틸리티 및 설정 +│ ├── 📁 axios/ # API 요청 헬퍼 axios instance +│ ├── 📁 utils/ # 유틸리티 함수들 (날짜 포맷터 등) +│ └── 📁 validators/ # 유효성 검사 +│ +├── 📁 assets/ # 정적 파일 +│ ├── 📁 icons/ # 아이콘 (SVG, PNG 등) +│ ├── 📁 images/ # 배너, 일러스트, 배경 이미지 +│ └── 📁 fonts/ # 웹폰트 (필요 시) +│ +├── 📁 hooks/ # 공통 커스텀 React 훅 +├── 📁 context/ # 전역 상태 (React Context API) +├── 📁 styles/ # 스타일 파일들 -> 테일윈드 전역 파일 +├── 📁 constants/ # 전역 상수 관리 +├── 📁 types/ # TypeScript 타입 정의 +│ +├── .env.local # 환경 변수 +├── tsconfig.json +├── package.json +└── next.config.ts +``` + +> 구현 팁 +> +> - `components/features/`는 **도메인 단위 UI**(예: `PostForm`, `PostList`) 중심. +> - `pages/`에서는 **라우팅과 데이터 주입**만 담당하도록 분리. +> - `lib/axios/`에 **axios instance**·인터셉터·에러 핸들러 배치. +> - `lib/validators/`는 Zod/Yup 중 택1, 폼과 API 경계에서 활용. +> - `constants/`에 라우트/키/에러 메시지 상수화 → 하드코딩 방지. + +--- + +## 🚀 빠른 시작 + +```bash +# 1) 설치 +pnpm install # 또는 yarn/npm + +# 2) 개발 서버 +pnpm dev # http://localhost:3000 + +# 3) 린트/포맷/빌드 +pnpm lint +pnpm format +pnpm build + +# 4) 스토리북 +pnpm storybook +``` + +### 환경변수 (.env.example) + +``` +NEXT_PUBLIC_API_BASE=... +``` + +> `next.config.ts`의 이미지 설정과 함께 사용합니다. + +--- + +## 🧭 라우팅(워크플로우) 메모 + +- `/` : 메인 +- `/list` : 룰패 목록 +- `/post` : 룰패(템플릿) 생성 +- `/post/{id}` : 룰패 메시지 확인 +- `/post/{id}/message` : 룰패 메시지 작성 +- `/post/{id}/edit` : 룰패 메시지 수정 +- (공통 UI) Modal/Toast/Dropdown 등은 `components/ui/`에서 제공 + +> 리포지토리에 `workflow.png`를 README와 같은 폴더에 두면 `![Workflow](./workflow.png)`로 바로 +> 미리보기 가능합니다. + +--- + +## 📝 문서 출처 + +- 노션: _중급 프로젝트 협업 문서_ (프로젝트 수행 계획서, RnR, 데일리 스크럼, 기술 스택 배경, + 프로젝트/세부 일정 CSV 등) + +### 메모 + +- 초기 셋업 TODO는 완료됨: ESLint/Prettier, Tailwind config(컬러/폰트), Storybook 공통, Vercel 배포 + 연동, GitHub/Discord 웹훅, 기본 폴더/아이콘 export, `.env.example`, util 함수 등. diff --git a/next.config.mjs b/next.config.mjs index 776efd8..a76190d 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -8,6 +8,11 @@ const nextConfig = { hostname: 'bootcamp-project-api.s3.ap-northeast-2.amazonaws.com', pathname: '/**', }, + { + protocol: 'https', + hostname: 'picsum.photos', + pathname: '/**', + }, ], }, }; diff --git a/package-lock.json b/package-lock.json index a4a04b8..d0db919 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "next": "14.2.32", "react": "^18", "react-dom": "^18", + "react-responsive": "^10.0.1", "tailwind-merge": "^3.3.1" }, "devDependencies": { @@ -21,13 +22,10 @@ "@storybook/addon-a11y": "^9.1.7", "@storybook/addon-docs": "^9.1.7", "@storybook/addon-themes": "^9.1.7", - "@storybook/addon-vitest": "^9.1.7", "@storybook/nextjs": "^9.1.8", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", - "@vitest/browser": "^3.2.4", - "@vitest/coverage-v8": "^3.2.4", "eslint": "^8", "eslint-config-next": "14.2.32", "eslint-config-prettier": "^10.1.8", @@ -43,8 +41,7 @@ "prettier-plugin-tailwindcss": "^0.6.14", "storybook": "^9.1.7", "tailwindcss": "^3.4.1", - "typescript": "^5", - "vitest": "^3.2.4" + "typescript": "^5" } }, "node_modules/@adobe/css-tools": { @@ -67,20 +64,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -1897,16 +1880,6 @@ "node": ">=6.9.0" } }, - "node_modules/@bcoe/v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", - "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/@chromatic-com/storybook": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/@chromatic-com/storybook/-/storybook-4.1.1.tgz", @@ -2139,16 +2112,6 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -2567,321 +2530,6 @@ "node": ">= 12" } }, - "node_modules/@polka/url": { - "version": "1.0.0-next.29", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", - "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.52.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.0.tgz", - "integrity": "sha512-VxDYCDqOaR7NXzAtvRx7G1u54d2kEHopb28YH/pKzY6y0qmogP3gG7CSiWsq9WvDFxOQMpNEyjVAHZFXfH3o/A==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.52.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.0.tgz", - "integrity": "sha512-pqDirm8koABIKvzL59YI9W9DWbRlTX7RWhN+auR8HXJxo89m4mjqbah7nJZjeKNTNYopqL+yGg+0mhCpf3xZtQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.52.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.0.tgz", - "integrity": "sha512-YCdWlY/8ltN6H78HnMsRHYlPiKvqKagBP1r+D7SSylxX+HnsgXGCmLiV3Y4nSyY9hW8qr8U9LDUx/Lo7M6MfmQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.52.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.0.tgz", - "integrity": "sha512-z4nw6y1j+OOSGzuVbSWdIp1IUks9qNw4dc7z7lWuWDKojY38VMWBlEN7F9jk5UXOkUcp97vA1N213DF+Lz8BRg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.52.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.0.tgz", - "integrity": "sha512-Q/dv9Yvyr5rKlK8WQJZVrp5g2SOYeZUs9u/t2f9cQ2E0gJjYB/BWoedXfUT0EcDJefi2zzVfhcOj8drWCzTviw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.52.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.0.tgz", - "integrity": "sha512-kdBsLs4Uile/fbjZVvCRcKB4q64R+1mUq0Yd7oU1CMm1Av336ajIFqNFovByipciuUQjBCPMxwJhCgfG2re3rg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.52.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.0.tgz", - "integrity": "sha512-aL6hRwu0k7MTUESgkg7QHY6CoqPgr6gdQXRJI1/VbFlUMwsSzPGSR7sG5d+MCbYnJmJwThc2ol3nixj1fvI/zQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.52.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.0.tgz", - "integrity": "sha512-BTs0M5s1EJejgIBJhCeiFo7GZZ2IXWkFGcyZhxX4+8usnIo5Mti57108vjXFIQmmJaRyDwmV59Tw64Ap1dkwMw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.52.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.0.tgz", - "integrity": "sha512-uj672IVOU9m08DBGvoPKPi/J8jlVgjh12C9GmjjBxCTQc3XtVmRkRKyeHSmIKQpvJ7fIm1EJieBUcnGSzDVFyw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.52.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.0.tgz", - "integrity": "sha512-/+IVbeDMDCtB/HP/wiWsSzduD10SEGzIZX2945KSgZRNi4TSkjHqRJtNTVtVb8IRwhJ65ssI56krlLik+zFWkw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.52.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.0.tgz", - "integrity": "sha512-U1vVzvSWtSMWKKrGoROPBXMh3Vwn93TA9V35PldokHGqiUbF6erSzox/5qrSMKp6SzakvyjcPiVF8yB1xKr9Pg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.52.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.0.tgz", - "integrity": "sha512-X/4WfuBAdQRH8cK3DYl8zC00XEE6aM472W+QCycpQJeLWVnHfkv7RyBFVaTqNUMsTgIX8ihMjCvFF9OUgeABzw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.52.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.0.tgz", - "integrity": "sha512-xIRYc58HfWDBZoLmWfWXg2Sq8VCa2iJ32B7mqfWnkx5mekekl0tMe7FHpY8I72RXEcUkaWawRvl3qA55og+cwQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.52.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.0.tgz", - "integrity": "sha512-mbsoUey05WJIOz8U1WzNdf+6UMYGwE3fZZnQqsM22FZ3wh1N887HT6jAOjXs6CNEK3Ntu2OBsyQDXfIjouI4dw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.52.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.0.tgz", - "integrity": "sha512-qP6aP970bucEi5KKKR4AuPFd8aTx9EF6BvutvYxmZuWLJHmnq4LvBfp0U+yFDMGwJ+AIJEH5sIP+SNypauMWzg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.52.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.0.tgz", - "integrity": "sha512-nmSVN+F2i1yKZ7rJNKO3G7ZzmxJgoQBQZ/6c4MuS553Grmr7WqR7LLDcYG53Z2m9409z3JLt4sCOhLdbKQ3HmA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.52.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.0.tgz", - "integrity": "sha512-2d0qRo33G6TfQVjaMR71P+yJVGODrt5V6+T0BDYH4EMfGgdC/2HWDVjSSFw888GSzAZUwuska3+zxNUCDco6rQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.52.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.0.tgz", - "integrity": "sha512-A1JalX4MOaFAAyGgpO7XP5khquv/7xKzLIyLmhNrbiCxWpMlnsTYr8dnsWM7sEeotNmxvSOEL7F65j0HXFcFsw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.52.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.0.tgz", - "integrity": "sha512-YQugafP/rH0eOOHGjmNgDURrpYHrIX0yuojOI8bwCyXwxC9ZdTd3vYkmddPX0oHONLXu9Rb1dDmT0VNpjkzGGw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.52.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.0.tgz", - "integrity": "sha512-zYdUYhi3Qe2fndujBqL5FjAFzvNeLxtIqfzNEVKD1I7C37/chv1VxhscWSQHTNfjPCrBFQMnynwA3kpZpZ8w4A==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.52.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.0.tgz", - "integrity": "sha512-fGk03kQylNaCOQ96HDMeT7E2n91EqvCDd3RwvT5k+xNdFCeMGnj5b5hEgTGrQuyidqSsD3zJDQ21QIaxXqTBJw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.52.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.0.tgz", - "integrity": "sha512-6iKDCVSIUQ8jPMoIV0OytRKniaYyy5EbY/RRydmLW8ZR3cEBhxbWl5ro0rkUNe0ef6sScvhbY79HrjRm8i3vDQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -2954,44 +2602,10 @@ "storybook": "^9.1.7" } }, - "node_modules/@storybook/addon-vitest": { - "version": "9.1.7", - "resolved": "https://registry.npmjs.org/@storybook/addon-vitest/-/addon-vitest-9.1.7.tgz", - "integrity": "sha512-LP+Tt0b/NyBCGATM+qHupNlX4Wm9d/LGDU0LPul29vPHpXnZ9Xd93mXEQ0ANnQNm5+zdTTUMFo5tlsgm0S19ZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@storybook/global": "^5.0.0", - "@storybook/icons": "^1.4.0", - "prompts": "^2.4.0", - "ts-dedent": "^2.2.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "@vitest/browser": "^3.0.0", - "@vitest/runner": "^3.0.0", - "storybook": "^9.1.7", - "vitest": "^3.0.0" - }, - "peerDependenciesMeta": { - "@vitest/browser": { - "optional": true - }, - "@vitest/runner": { - "optional": true - }, - "vitest": { - "optional": true - } - } - }, - "node_modules/@storybook/builder-webpack5": { - "version": "9.1.8", - "resolved": "https://registry.npmjs.org/@storybook/builder-webpack5/-/builder-webpack5-9.1.8.tgz", - "integrity": "sha512-xnJNmqmwgoIeJ4gIDqIBI68qTyzJUYyThKpD1DJx6l+0caBxebEPoYxCzC9vKI0Thb6VzjCpmJXxDUJJpx8zsw==", + "node_modules/@storybook/builder-webpack5": { + "version": "9.1.8", + "resolved": "https://registry.npmjs.org/@storybook/builder-webpack5/-/builder-webpack5-9.1.8.tgz", + "integrity": "sha512-xnJNmqmwgoIeJ4gIDqIBI68qTyzJUYyThKpD1DJx6l+0caBxebEPoYxCzC9vKI0Thb6VzjCpmJXxDUJJpx8zsw==", "dev": true, "license": "MIT", "dependencies": { @@ -3451,6 +3065,7 @@ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -3471,6 +3086,7 @@ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "dequal": "^2.0.3" } @@ -3532,7 +3148,8 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -4266,76 +3883,6 @@ "win32" ] }, - "node_modules/@vitest/browser": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-3.2.4.tgz", - "integrity": "sha512-tJxiPrWmzH8a+w9nLKlQMzAKX/7VjFs50MWgcAj7p9XQ7AQ9/35fByFYptgPELyLw+0aixTnC4pUWV+APcZ/kw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@testing-library/dom": "^10.4.0", - "@testing-library/user-event": "^14.6.1", - "@vitest/mocker": "3.2.4", - "@vitest/utils": "3.2.4", - "magic-string": "^0.30.17", - "sirv": "^3.0.1", - "tinyrainbow": "^2.0.0", - "ws": "^8.18.2" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "playwright": "*", - "vitest": "3.2.4", - "webdriverio": "^7.0.0 || ^8.0.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "playwright": { - "optional": true - }, - "safaridriver": { - "optional": true - }, - "webdriverio": { - "optional": true - } - } - }, - "node_modules/@vitest/coverage-v8": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz", - "integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.3.0", - "@bcoe/v8-coverage": "^1.0.2", - "ast-v8-to-istanbul": "^0.3.3", - "debug": "^4.4.1", - "istanbul-lib-coverage": "^3.2.2", - "istanbul-lib-report": "^3.0.1", - "istanbul-lib-source-maps": "^5.0.6", - "istanbul-reports": "^3.1.7", - "magic-string": "^0.30.17", - "magicast": "^0.3.5", - "std-env": "^3.9.0", - "test-exclude": "^7.0.1", - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@vitest/browser": "3.2.4", - "vitest": "3.2.4" - }, - "peerDependenciesMeta": { - "@vitest/browser": { - "optional": true - } - } - }, "node_modules/@vitest/expect": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", @@ -4393,36 +3940,6 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/runner": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", - "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "3.2.4", - "pathe": "^2.0.3", - "strip-literal": "^3.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", - "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "3.2.4", - "magic-string": "^0.30.17", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, "node_modules/@vitest/spy": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", @@ -5106,25 +4623,6 @@ "dev": true, "license": "MIT" }, - "node_modules/ast-v8-to-istanbul": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.5.tgz", - "integrity": "sha512-9SdXjNheSiE8bALAQCQQuT6fgQaoxJh7IRYrRGZ8/9nv8WhJeC1aXAwN8TbaOssGOukUvyvnkgD9+Yuykvl1aA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.30", - "estree-walker": "^3.0.3", - "js-tokens": "^9.0.1" - } - }, - "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", - "dev": true, - "license": "MIT" - }, "node_modules/async-function": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", @@ -5722,16 +5220,6 @@ "node": ">=10.16.0" } }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -6295,6 +5783,12 @@ } } }, + "node_modules/css-mediaquery": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/css-mediaquery/-/css-mediaquery-0.1.2.tgz", + "integrity": "sha512-COtn4EROW5dBGlE/4PiKnh6rZpAPxDeFLaEEwt4i10jpDMFt2EhQGS79QmmrO+iKCHv0PU/HrOWEhijFd1x99Q==", + "license": "BSD" + }, "node_modules/css-select": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", @@ -6526,6 +6020,7 @@ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=6" } @@ -6592,7 +6087,8 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/dom-converter": { "version": "0.2.0", @@ -7669,16 +7165,6 @@ "safe-buffer": "^5.1.1" } }, - "node_modules/expect-type": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", - "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -8435,13 +7921,6 @@ ], "license": "MIT" }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, "node_modules/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -8534,6 +8013,12 @@ "dev": true, "license": "MIT" }, + "node_modules/hyphenate-style-name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz", + "integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==", + "license": "BSD-3-Clause" + }, "node_modules/icss-utils": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", @@ -9180,76 +8665,6 @@ "dev": true, "license": "ISC" }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-report/node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/iterator.prototype": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", @@ -9440,16 +8855,6 @@ "json-buffer": "3.0.1" } }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/language-subtag-registry": { "version": "0.3.23", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", @@ -9631,6 +9036,7 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -9645,18 +9051,6 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/magicast": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", - "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.25.4", - "@babel/types": "^7.25.4", - "source-map-js": "^1.2.0" - } - }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -9683,6 +9077,15 @@ "semver": "bin/semver.js" } }, + "node_modules/matchmediaquery": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/matchmediaquery/-/matchmediaquery-0.4.2.tgz", + "integrity": "sha512-wrZpoT50ehYOudhDjt/YvUJc6eUzcdFPdmbizfgvswCKNHD1/OBOHYJpHie+HXpu6bSkEGieFMYk6VuutaiRfA==", + "license": "MIT", + "dependencies": { + "css-mediaquery": "^0.1.2" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -9847,16 +9250,6 @@ "node": ">=16 || 14 >=14.17" } }, - "node_modules/mrmime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", - "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -10107,7 +9500,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -10373,13 +9765,6 @@ "node": ">=6" } }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -10529,13 +9914,6 @@ "node": ">=8" } }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, "node_modules/pathval": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", @@ -11152,6 +10530,7 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -11167,6 +10546,7 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -11179,7 +10559,8 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/process": { "version": "0.11.10", @@ -11198,25 +10579,10 @@ "dev": true, "license": "MIT" }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", @@ -11413,7 +10779,6 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, "license": "MIT" }, "node_modules/react-refresh": { @@ -11426,6 +10791,24 @@ "node": ">=0.10.0" } }, + "node_modules/react-responsive": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/react-responsive/-/react-responsive-10.0.1.tgz", + "integrity": "sha512-OM5/cRvbtUWEX8le8RCT8scA8y2OPtb0Q/IViEyCEM5FBN8lRrkUOZnu87I88A6njxDldvxG+rLBxWiA7/UM9g==", + "license": "MIT", + "dependencies": { + "hyphenate-style-name": "^1.0.0", + "matchmediaquery": "^0.4.2", + "prop-types": "^15.6.1", + "shallow-equal": "^3.1.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -11860,48 +11243,6 @@ "dev": true, "license": "MIT" }, - "node_modules/rollup": { - "version": "4.52.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.0.tgz", - "integrity": "sha512-+IuescNkTJQgX7AkIDtITipZdIGcWF0pnVvZTWStiazUmcGA2ag8dfg0urest2XlXUi9kuhfQ+qmdc5Stc3z7g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.52.0", - "@rollup/rollup-android-arm64": "4.52.0", - "@rollup/rollup-darwin-arm64": "4.52.0", - "@rollup/rollup-darwin-x64": "4.52.0", - "@rollup/rollup-freebsd-arm64": "4.52.0", - "@rollup/rollup-freebsd-x64": "4.52.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.52.0", - "@rollup/rollup-linux-arm-musleabihf": "4.52.0", - "@rollup/rollup-linux-arm64-gnu": "4.52.0", - "@rollup/rollup-linux-arm64-musl": "4.52.0", - "@rollup/rollup-linux-loong64-gnu": "4.52.0", - "@rollup/rollup-linux-ppc64-gnu": "4.52.0", - "@rollup/rollup-linux-riscv64-gnu": "4.52.0", - "@rollup/rollup-linux-riscv64-musl": "4.52.0", - "@rollup/rollup-linux-s390x-gnu": "4.52.0", - "@rollup/rollup-linux-x64-gnu": "4.52.0", - "@rollup/rollup-linux-x64-musl": "4.52.0", - "@rollup/rollup-openharmony-arm64": "4.52.0", - "@rollup/rollup-win32-arm64-msvc": "4.52.0", - "@rollup/rollup-win32-ia32-msvc": "4.52.0", - "@rollup/rollup-win32-x64-gnu": "4.52.0", - "@rollup/rollup-win32-x64-msvc": "4.52.0", - "fsevents": "~2.3.2" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -12209,6 +11550,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/shallow-equal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-3.1.0.tgz", + "integrity": "sha512-pfVOw8QZIXpMbhBWvzBISicvToTiM5WBF1EeAUZDDSb5Dt29yl4AYbyywbJFSEsRUMr7gJaxqCdr4L3tQf9wVg==", + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -12308,13 +11655,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" - }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -12328,28 +11668,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/sirv": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", - "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@polka/url": "^1.0.0-next.24", - "mrmime": "^2.0.0", - "totalist": "^3.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true, - "license": "MIT" - }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -12387,13 +11705,6 @@ "dev": true, "license": "MIT" }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" - }, "node_modules/stackframe": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", @@ -12401,13 +11712,6 @@ "dev": true, "license": "MIT" }, - "node_modules/std-env": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", - "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", - "dev": true, - "license": "MIT" - }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", @@ -12776,26 +12080,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strip-literal": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz", - "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", - "dev": true, - "license": "MIT", - "dependencies": { - "js-tokens": "^9.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/strip-literal/node_modules/js-tokens": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", - "dev": true, - "license": "MIT" - }, "node_modules/style-loader": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz", @@ -13060,84 +12344,6 @@ "dev": true, "license": "MIT" }, - "node_modules/test-exclude": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", - "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^10.4.1", - "minimatch": "^9.0.4" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/test-exclude/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/test-exclude/node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/test-exclude/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -13188,20 +12394,6 @@ "dev": true, "license": "MIT" }, - "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", - "dev": true, - "license": "MIT" - }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -13250,16 +12442,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/tinypool": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", - "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.0.0 || >=20.0.0" - } - }, "node_modules/tinyrainbow": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", @@ -13308,16 +12490,6 @@ "node": ">=8.0" } }, - "node_modules/totalist": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", - "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -13768,221 +12940,6 @@ "dev": true, "license": "MIT" }, - "node_modules/vite": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.6.tgz", - "integrity": "sha512-SRYIB8t/isTwNn8vMB3MR6E+EQZM/WG1aKmmIUCfDXfVvKfc20ZpamngWHKzAmmu9ppsgxsg4b2I7c90JZudIQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "lightningcss": "^1.21.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vite-node": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", - "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cac": "^6.7.14", - "debug": "^4.4.1", - "es-module-lexer": "^1.7.0", - "pathe": "^2.0.3", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" - }, - "bin": { - "vite-node": "vite-node.mjs" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/vite/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/vite/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/vitest": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", - "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/chai": "^5.2.2", - "@vitest/expect": "3.2.4", - "@vitest/mocker": "3.2.4", - "@vitest/pretty-format": "^3.2.4", - "@vitest/runner": "3.2.4", - "@vitest/snapshot": "3.2.4", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", - "chai": "^5.2.0", - "debug": "^4.4.1", - "expect-type": "^1.2.1", - "magic-string": "^0.30.17", - "pathe": "^2.0.3", - "picomatch": "^4.0.2", - "std-env": "^3.9.0", - "tinybench": "^2.9.0", - "tinyexec": "^0.3.2", - "tinyglobby": "^0.2.14", - "tinypool": "^1.1.1", - "tinyrainbow": "^2.0.0", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", - "vite-node": "3.2.4", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@types/debug": "^4.1.12", - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.2.4", - "@vitest/ui": "3.2.4", - "happy-dom": "*", - "jsdom": "*" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@types/debug": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - } - } - }, - "node_modules/vitest/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/vm-browserify": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", @@ -14240,23 +13197,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", diff --git a/package.json b/package.json index c15a929..face2b9 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "next": "14.2.32", "react": "^18", "react-dom": "^18", + "react-responsive": "^10.0.1", "tailwind-merge": "^3.3.1" }, "devDependencies": { @@ -26,13 +27,10 @@ "@storybook/addon-a11y": "^9.1.7", "@storybook/addon-docs": "^9.1.7", "@storybook/addon-themes": "^9.1.7", - "@storybook/addon-vitest": "^9.1.7", "@storybook/nextjs": "^9.1.8", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", - "@vitest/browser": "^3.2.4", - "@vitest/coverage-v8": "^3.2.4", "eslint": "^8", "eslint-config-next": "14.2.32", "eslint-config-prettier": "^10.1.8", @@ -48,7 +46,6 @@ "prettier-plugin-tailwindcss": "^0.6.14", "storybook": "^9.1.7", "tailwindcss": "^3.4.1", - "typescript": "^5", - "vitest": "^3.2.4" + "typescript": "^5" } } diff --git a/public/fallback.png b/public/fallback.png new file mode 100644 index 0000000..28e0904 Binary files /dev/null and b/public/fallback.png differ diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..d6b56a6 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/favicon.png b/public/favicon.png new file mode 100644 index 0000000..3bd6d01 Binary files /dev/null and b/public/favicon.png differ diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..032bc43 --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/api/alerts.ts b/src/api/alerts.ts new file mode 100644 index 0000000..d6b34fb --- /dev/null +++ b/src/api/alerts.ts @@ -0,0 +1,30 @@ +// src/api/alerts.ts +import axios from '@/lib/axios'; +import type { ApiResponse, PaginatedResponse } from '@/types/api'; +import type { Notice } from '@/types/notice'; +import type { Shop } from '@/types/shop'; + +// 서버 응답에 맞춘 타입 +export type AlertItem = { + id: string; + createdAt: string; + result: 'accepted' | 'rejected'; + read: boolean; + application: { item: { id: string; status: 'pending' | 'accepted' | 'rejected' } }; + shop: { item: Shop }; + notice: { item: Notice }; +}; + +export async function getUserAlerts(userId: string, params?: { offset?: number; limit?: number }) { + const { data } = await axios.get[] }>( + `/users/${userId}/alerts`, + { params } + ); + return data; +} + +export async function markAlertRead(userId: string, alertId: string) { + // PUT /users/{user_id}/alerts/{alert_id} (Body 없이 호출해도 OK) + const { data } = await axios.put<{ item: AlertItem }>(`/users/${userId}/alerts/${alertId}`); + return data.item; +} diff --git a/src/api/applications.ts b/src/api/applications.ts new file mode 100644 index 0000000..aaf4d33 --- /dev/null +++ b/src/api/applications.ts @@ -0,0 +1,91 @@ +import axiosInstance from '@/lib/axios'; +import type { ApiResponse } from '@/types/api'; +import { ApplicationItem, ApplicationListResponse } from '@/types/applications'; +import { NoticeCard } from '@/types/notice'; + +// 유저의 공고 지원 내역 전체 조회 +export async function getAllUserApplications({ + userId, + limit = 10, +}: { + userId: string; + limit?: number; +}) { + const results: ApiResponse[] = []; + let offset = 0; + let hasNext = true; + + while (hasNext) { + const { data } = await axiosInstance.get( + `/users/${userId}/applications`, + { params: { offset, limit } } + ); + + results.push(...data.items); + hasNext = data.hasNext; + offset += limit; + } + + return results; // 모든 페이지 합쳐 반환 +} + +// 가게의 특정 공고 지원 등록 +export const postApplication = async (shopId: string, noticeId: string) => { + await axiosInstance.post(`/shops/${shopId}/notices/${noticeId}/applications`); +}; + +// 가게의 특정 공고 지원 취소 +export const putApplication = async (shopId: string, noticeId: string, applicationId: string) => { + await axiosInstance.put(`/shops/${shopId}/notices/${noticeId}/applications/${applicationId}`, { + status: 'canceled', + }); +}; + +// 특정 가게 특정 공고 정보 +export async function getNoticeById(shopId: string, noticeId: string): Promise { + const { data } = await axiosInstance.get(`/shops/${shopId}/notices/${noticeId}`); + + // API 응답 -> NoticeCard 형태로 매핑 + const noticeCard: NoticeCard = { + id: data.id, + name: data.shop?.name ?? '', + shopId: data.shop?.id ?? shopId, + address1: data.shop?.address1 ?? '', + hourlyPay: data.hourlyPay ?? 0, + originalHourlyPay: data.originalHourlyPay ?? data.hourlyPay ?? 0, + workhour: data.workhour ?? 0, + startsAt: data.startsAt, + closed: data.closed ?? false, + imageUrl: data.imageUrl ?? '', + description: data.description ?? '', + category: data.category ?? '기타', // 누락 필수 + shopDescription: data.shopDescription ?? '', // 누락 필수 + }; + + return noticeCard; +} + +// 특정 가게 특정 공고 지원자 목록 +export async function getApplications( + shopId: string, + noticeId: string, + offset = 0, + limit = 5 +): Promise { + const { data } = await axiosInstance.get( + `/shops/${shopId}/notices/${noticeId}/applications`, + { params: { offset, limit } } + ); + const applications: ApplicationItem[] = data.items.map(resp => resp.item); + + return applications; +} + +// 신청 상태 업데이트 +export const updateApplicationStatus = async ( + applicationId: string, + status: 'accepted' | 'rejected' +) => { + const res = await axiosInstance.put(`/applications/${applicationId}/status`, { status }); + return res.data; +}; diff --git a/src/api/auth.ts b/src/api/auth.ts new file mode 100644 index 0000000..b6c01e6 --- /dev/null +++ b/src/api/auth.ts @@ -0,0 +1,15 @@ +// 목적: 로그인/회원가입 API만 담당(다른 리소스와 분리) +import axios from '@/lib/axios'; +import type { LoginRequest, LoginResponse, UserRequest } from '@/types/user'; + +// 로그인: POST /token +export async function apiLogin(body: LoginRequest) { + const { data } = await axios.post('/token', body); + return data; // data.item.token, data.item.user.item.id 사용 +} + +// 회원가입: POST /users (알바생이면 type: 'employee') +export async function apiSignup(body: UserRequest) { + const { data } = await axios.post('/users', body); + return data; +} diff --git a/src/api/employer.ts b/src/api/employer.ts new file mode 100644 index 0000000..969d035 --- /dev/null +++ b/src/api/employer.ts @@ -0,0 +1,66 @@ +import axios from '@/lib/axios'; +import { RegisterFormData } from '@/types/myShop'; +import { default as originAxios } from 'axios'; + +export async function postShop(body: Omit) { + const accessToken = localStorage.getItem('thejulge-token'); + const { data } = await axios.post('/shops', body, { + headers: { Authorization: `Bearer ${accessToken}` }, + }); + return data; +} + +export async function getShop(shopId: string) { + const { data } = await axios.get(`/shops/${shopId}`); + return data; +} + +export async function putShop(shopId: string, body: RegisterFormData) { + const accessToken = localStorage.getItem('thejulge-token'); + const { data } = await axios.put(`/shops/${shopId}`, body, { + headers: { Authorization: `Bearer ${accessToken}` }, + }); + return data; +} + +export async function getNotice(shopId: string, params?: { offset?: number; limit?: number }) { + const { data } = await axios.get(`/shops/${shopId}/notices`, { params }); + return data; +} + +/////////////////////////////////////////////////////////////////////////////// + +export async function postPresignedUrl(imageUrl: string) { + const accessToken = localStorage.getItem('thejulge-token'); + const { data } = await axios.post( + '/images', + { name: imageUrl }, + { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + } + ); + return data.item.url; +} + +export async function uploadImage(presignedUrl: string, file: File) { + try { + await originAxios.put(presignedUrl, file); + } catch (error) { + alert(error); + } +} + +export async function getPresignedUrl(presignedUrl: string) { + // 1. URL 객체 생성 + const url = new URL(presignedUrl); + + url.search = ''; + + // 3. 쿼리 파라미터가 제거된 새 URL 문자열을 얻습니다. + const baseUrl = url.toString(); + + const result = await originAxios.get(baseUrl); + return result; +} diff --git a/src/api/index.ts b/src/api/index.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/api/users.ts b/src/api/users.ts new file mode 100644 index 0000000..9e6a128 --- /dev/null +++ b/src/api/users.ts @@ -0,0 +1,15 @@ +// 목적: 유저 리소스 전용(내 정보 조회/수정) +import axios from '@/lib/axios'; +import type { User } from '@/types/user'; + +// 내 정보 조회: GET /users/{user_id} +export async function apiGetUser(userId: string) { + const { data } = await axios.get<{ item: User }>(`/users/${userId}`); + return data.item; +} + +// 내 정보 수정: PUT /users/{user_id} +export async function apiUpdateUser(userId: string, patch: Partial) { + const { data } = await axios.put<{ item: User }>(`/users/${userId}`, patch); + return data.item; +} diff --git a/src/assets/icon/ic-calendar-clock.svg b/src/assets/icon/ic-calendar-clock.svg new file mode 100644 index 0000000..3a818ef --- /dev/null +++ b/src/assets/icon/ic-calendar-clock.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/assets/icon/ic-coins.svg b/src/assets/icon/ic-coins.svg new file mode 100644 index 0000000..2ea42d0 --- /dev/null +++ b/src/assets/icon/ic-coins.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/assets/icon/ic-map-pin.svg b/src/assets/icon/ic-map-pin.svg new file mode 100644 index 0000000..b35956a --- /dev/null +++ b/src/assets/icon/ic-map-pin.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icon/ic-result-badge.svg b/src/assets/icon/ic-result-badge.svg new file mode 100644 index 0000000..37226f1 --- /dev/null +++ b/src/assets/icon/ic-result-badge.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/logo.svg b/src/assets/images/logo.svg index 36cff94..11d7469 100644 --- a/src/assets/images/logo.svg +++ b/src/assets/images/logo.svg @@ -1,10 +1,10 @@ - - - - - - - - - + + + + + + + + + diff --git a/src/components/common/index.ts b/src/components/common/index.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/components/features/index.ts b/src/components/features/index.ts index e69de29..401c83d 100644 --- a/src/components/features/index.ts +++ b/src/components/features/index.ts @@ -0,0 +1 @@ +export { CustomNotice, NoticeEmpty, NoticeListSection, RecentNoticeList } from './noticeList'; diff --git a/src/components/features/my-shop/indexModal.tsx b/src/components/features/my-shop/indexModal.tsx new file mode 100644 index 0000000..eb0a962 --- /dev/null +++ b/src/components/features/my-shop/indexModal.tsx @@ -0,0 +1,46 @@ +import { Modal } from '@/components/ui'; +import { useRouter } from 'next/router'; + +interface Props { + guestRedirect: boolean; + setGuestRedirect: (value: boolean) => void; + employeeRedirect: boolean; + setEmployeeRedirect: (value: boolean) => void; +} + +const IndexModal = ({ + guestRedirect, + setGuestRedirect, + employeeRedirect, + setEmployeeRedirect, +}: Props) => { + const router = useRouter(); + return ( + <> + setGuestRedirect(false)} + variant='warning' + title='로그인이 필요합니다.' + primaryText='확인' + onPrimary={() => { + setGuestRedirect(false); + router.push('/login'); + }} + /> + setEmployeeRedirect(false)} + variant='warning' + title='접근권한이 없습니다.' + primaryText='확인' + onPrimary={() => { + setEmployeeRedirect(false); + router.push('/'); + }} + /> + + ); +}; + +export default IndexModal; diff --git a/src/components/features/my-shop/registerAddress.tsx b/src/components/features/my-shop/registerAddress.tsx new file mode 100644 index 0000000..abf34ed --- /dev/null +++ b/src/components/features/my-shop/registerAddress.tsx @@ -0,0 +1,43 @@ +import { Dropdown, Input } from '@/components/ui'; +import { ADDRESS_CODE } from '@/constants/dropdown'; +import { RegisterFormData } from '@/types/myShop'; + +interface Props { + formData: RegisterFormData; + handleChange: (key: keyof RegisterFormData, value: string) => void; +} + +const RegisterAddress = ({ formData, handleChange }: Props) => { + return ( + <> +
+
+
+ 주소 + * +
+ handleChange('address1', val)} + className='w-full' + /> +
+
+ handleChange('address2', e.target.value)} + placeholder='입력' + /> +
+
+ + ); +}; + +export default RegisterAddress; diff --git a/src/components/features/my-shop/registerDescription.tsx b/src/components/features/my-shop/registerDescription.tsx new file mode 100644 index 0000000..badaaa0 --- /dev/null +++ b/src/components/features/my-shop/registerDescription.tsx @@ -0,0 +1,24 @@ +import { RegisterFormData } from '@/types/myShop'; + +interface Props { + formData: RegisterFormData; + handleChange: (key: keyof RegisterFormData, value: string) => void; +} + +const RegisterDescription = ({ formData, handleChange }: Props) => { + return ( + <> +
+ 가게 설명 + +
+ + ); +}; + +export default RegisterDescription; diff --git a/src/components/features/my-shop/registerImage.tsx b/src/components/features/my-shop/registerImage.tsx new file mode 100644 index 0000000..f982160 --- /dev/null +++ b/src/components/features/my-shop/registerImage.tsx @@ -0,0 +1,56 @@ +import { Icon } from '@/components/ui'; +import Image from 'next/image'; +import { ChangeEvent } from 'react'; + +interface Props { + mode: string; + preview: string | null; + handleImageChange: (e: ChangeEvent) => void; +} + +const RegisterImage = ({ mode, preview, handleImageChange }: Props) => { + return ( + <> +
+
+ 가게 이미지 + * +
+ +
+ + ); +}; + +export default RegisterImage; diff --git a/src/components/features/my-shop/registerModal.tsx b/src/components/features/my-shop/registerModal.tsx new file mode 100644 index 0000000..e6868ca --- /dev/null +++ b/src/components/features/my-shop/registerModal.tsx @@ -0,0 +1,59 @@ +import { Modal } from '@/components/ui'; +import { useRouter } from 'next/router'; + +interface Props { + mode: string; + openWarning: boolean; + setOpenWarning: (value: boolean) => void; + openCancel: boolean; + setOpenCancel: (value: boolean) => void; + openConfirm: boolean; + setOepnConfirm: (value: boolean) => void; +} + +const RegisterModal = ({ + mode, + openWarning, + setOpenWarning, + openCancel, + setOpenCancel, + openConfirm, + setOepnConfirm, +}: Props) => { + const router = useRouter(); + return ( + <> + setOpenWarning(false)} + variant='warning' + title='필수 항목을 작성해주세요.' + primaryText='확인' + onPrimary={() => setOpenWarning(false)} + /> + setOpenCancel(false)} + variant='warning' + title='취소하시겠습니까?' + primaryText='아니요' + secondaryText='예' + onSecondary={() => { + setOpenCancel(false); + router.push('/my-shop'); + }} + onPrimary={() => setOpenCancel(false)} + /> + setOepnConfirm(false)} + variant='success' + title={mode === 'edit' ? '수정이 완료되었습니다.' : '등록이 완료되었습니다.'} + primaryText='확인' + onPrimary={() => router.push('/my-shop')} + /> + + ); +}; + +export default RegisterModal; diff --git a/src/components/features/my-shop/registerName.tsx b/src/components/features/my-shop/registerName.tsx new file mode 100644 index 0000000..9412c6a --- /dev/null +++ b/src/components/features/my-shop/registerName.tsx @@ -0,0 +1,43 @@ +import { Dropdown, Input } from '@/components/ui'; +import { CATEGORY_CODE } from '@/constants/dropdown'; +import { RegisterFormData } from '@/types/myShop'; + +interface Props { + formData: RegisterFormData; + handleChange: (key: keyof RegisterFormData, value: string) => void; +} + +const RegisterName = ({ formData, handleChange }: Props) => { + return ( + <> +
+
+ handleChange('name', e.target.value)} + placeholder='입력' + /> +
+
+
+ 분류 + * +
+ handleChange('category', val)} + className='w-full' + /> +
+
+ + ); +}; + +export default RegisterName; diff --git a/src/components/features/my-shop/registerWage.tsx b/src/components/features/my-shop/registerWage.tsx new file mode 100644 index 0000000..1253c52 --- /dev/null +++ b/src/components/features/my-shop/registerWage.tsx @@ -0,0 +1,30 @@ +import { Input } from '@/components/ui'; +import { RegisterFormData } from '@/types/myShop'; +import { ChangeEvent } from 'react'; + +interface Props { + formData: RegisterFormData; + handleWageChange: (e: ChangeEvent) => void; +} + +const RegisterWage = ({ formData, handleWageChange }: Props) => { + return ( + <> +
+
+ +
+
+
+ + ); +}; + +export default RegisterWage; diff --git a/src/components/features/my-shop/shopForm.tsx b/src/components/features/my-shop/shopForm.tsx new file mode 100644 index 0000000..beaf517 --- /dev/null +++ b/src/components/features/my-shop/shopForm.tsx @@ -0,0 +1,122 @@ +import RegisterAddress from '@/components/features/my-shop/registerAddress'; +import RegisterDescription from '@/components/features/my-shop/registerDescription'; +import RegisterImage from '@/components/features/my-shop/registerImage'; +import RegisterModal from '@/components/features/my-shop/registerModal'; +import RegisterName from '@/components/features/my-shop/registerName'; +import RegisterWage from '@/components/features/my-shop/registerWage'; +import { Container } from '@/components/layout'; +import { Button, Icon } from '@/components/ui'; +import { RegisterFormData } from '@/types/myShop'; +import { ChangeEvent, useEffect, useState } from 'react'; + +interface ShopFromProps { + mode: 'register' | 'edit'; + initialData?: RegisterFormData | null; + onSubmit: (data: RegisterFormData) => Promise; +} + +const ShopForm = ({ mode, initialData, onSubmit }: ShopFromProps) => { + const [formData, setFormData] = useState( + initialData ?? { + name: '', + category: undefined, + address1: undefined, + address2: '', + originalHourlyPay: '', + description: '', + image: null, + imageUrl: '', + } + ); + + useEffect(() => { + if (initialData) { + setFormData(initialData); + if (initialData.imageUrl) { + setPreview(initialData.imageUrl); + } + } + }, [initialData]); + + const [preview, setPreview] = useState(null); + const [openWarning, setOpenWarning] = useState(false); + const [openCancel, setOpenCancel] = useState(false); + const [openConfirm, setOepnConfirm] = useState(false); + + const handleChange = (key: keyof RegisterFormData, value: string) => { + setFormData(prev => ({ ...prev, [key]: value })); + }; + + const handleWageChange = (e: ChangeEvent) => { + const raw = e.target.value.replace(/,/g, ''); + if (!/^\d*$/.test(raw)) return; + const formatted = raw ? Number(raw).toLocaleString() : ''; + setFormData(prev => ({ ...prev, originalHourlyPay: formatted })); + }; + + const handleImageChange = async (e: ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; + setFormData(prev => ({ ...prev, image: file })); + setPreview(URL.createObjectURL(file)); + }; + + const validateForm = () => { + if (mode === 'register') { + return ( + !formData.name || + !formData.category || + !formData.address1 || + !formData.address2 || + !formData.originalHourlyPay || + (!formData.image && !formData.imageUrl) + ); + } + return false; + }; + + const handleSubmit = async () => { + if (validateForm()) { + setOpenWarning(true); + return; + } + setOepnConfirm(true); + await onSubmit(formData); + }; + + return ( + <> +
+ +
+

+ {mode === 'register' ? '가게 등록' : '가게 편집'} +

+ +
+ + + + + + + +
+
+ + ); +}; +export default ShopForm; diff --git a/src/components/features/noticeList/customNotice.tsx b/src/components/features/noticeList/customNotice.tsx new file mode 100644 index 0000000..bc5f093 --- /dev/null +++ b/src/components/features/noticeList/customNotice.tsx @@ -0,0 +1,42 @@ +import { Container, HorizontalScroll } from '@/components/layout'; +import { Post, SkeletonUI } from '@/components/ui'; +import useAuth from '@/hooks/useAuth'; +import useCustomNotices from './hooks/useCustomNotices'; + +const CustomNoticeList = () => { + const { user } = useAuth(); + const { notices, isLoading, error } = useCustomNotices(user?.address); + + if (error) { + return
{error}
; + } + + return ( + <> + {isLoading ? ( + + ) : ( + + {notices.map(notice => ( +
  • + +
  • + ))} +
    + )} + + ); +}; + +const CustomNotice = () => ( +
    + +

    맞춤공고

    + +
    +
    +); +export default CustomNotice; diff --git a/src/components/features/noticeList/hooks/useCustomNotices.ts b/src/components/features/noticeList/hooks/useCustomNotices.ts new file mode 100644 index 0000000..c83ef17 --- /dev/null +++ b/src/components/features/noticeList/hooks/useCustomNotices.ts @@ -0,0 +1,55 @@ +import useAsync from '@/hooks/useAsync'; +import axiosInstance from '@/lib/axios'; +import { paramsSerializer } from '@/lib/utils/paramsSerializer'; +import { toPostCard } from '@/lib/utils/parse'; +import { NoticeQuery } from '@/types/api'; +import { PostCard } from '@/types/notice'; +import { useCallback, useEffect } from 'react'; + +const useCustomNotices = (address?: string) => { + const { data, isLoading, error, fetch } = useAsync(); + + const fetchCustom = useCallback(async () => { + const now = new Date(); + now.setSeconds(now.getSeconds() + 15); + + // 기본 쿼리 + const baseQuery: NoticeQuery = { + sort: 'time', + startsAtGte: now.toISOString(), + limit: 3, + }; + + const firstQuery: NoticeQuery = address ? { ...baseQuery, address: [address] } : baseQuery; + const getCustom = axiosInstance + .get('/notices', { + params: firstQuery, + paramsSerializer: { serialize: paramsSerializer }, + }) + .then(async res => { + const items = res.data.items.map(toPostCard); + if (items.length === 0) { + const fallbackRes = await axiosInstance.get('/notices', { + params: baseQuery, + paramsSerializer: { serialize: paramsSerializer }, + }); + return fallbackRes.data.items.map(toPostCard); + } + return items; + }); + + await fetch(getCustom); + }, [address, fetch]); + + useEffect(() => { + fetchCustom(); + }, []); + + return { + notices: data ?? [], + isLoading, + error, + fetchCustom, + }; +}; +export default useCustomNotices; diff --git a/src/components/features/noticeList/hooks/useNotices.ts b/src/components/features/noticeList/hooks/useNotices.ts new file mode 100644 index 0000000..7801941 --- /dev/null +++ b/src/components/features/noticeList/hooks/useNotices.ts @@ -0,0 +1,86 @@ +import useAsync from '@/hooks/useAsync'; +import axiosInstance from '@/lib/axios'; +import { paramsSerializer } from '@/lib/utils/paramsSerializer'; +import { toPostCard } from '@/lib/utils/parse'; +import { NoticeQuery, PaginatedResponse } from '@/types/api'; +import { PostCard } from '@/types/notice'; +import { useCallback, useState } from 'react'; + +const INIT_FILTER_DATA: NoticeQuery = { + sort: 'time', +}; + +const useNotices = (initialQuery: Partial = {}) => { + const { data, isLoading, isInitialized, error, fetch } = useAsync(); + const [filters, setFiltersState] = useState(INIT_FILTER_DATA); + const [pagination, setPagination] = useState({ + offset: 0, + limit: 6, + count: 0, + hasNext: false, + }); + + const changeTimeFilter = useCallback((q: Partial): Partial => { + const now = new Date(); + now.setSeconds(now.getSeconds() + 15); // 서버 시간 오차 대비 + + // startsAtGte가 없거나, 현재보다 과거면 현재 시각으로 보정 + if (!q.startsAtGte || new Date(q.startsAtGte) < now) { + return { ...q, startsAtGte: now.toISOString() }; + } + + return q; + }, []); + + const fetchNotices = useCallback( + async (query?: Partial) => { + // 검색 필터 업데이트 + const mergedFilter: NoticeQuery = { + ...filters, // 내부 초기값 + limit: pagination.limit, + offset: pagination.offset, + ...initialQuery, // 외부 초기값 + ...(query ?? {}), // fetchNotices 호출 시 추가 값 + }; + const queryUpdate = changeTimeFilter(mergedFilter) as NoticeQuery; + // 상태에도 반영하여 UI와 동기화 + + setFiltersState(prev => ({ ...prev, ...queryUpdate })); + // 필터기반 패치 + const getNotices = axiosInstance + .get('/notices', { + params: queryUpdate, + paramsSerializer: { serialize: paramsSerializer }, + }) + .then(res => { + setPagination({ + offset: res.data.offset, + limit: res.data.limit, + count: res.data.count, + hasNext: res.data.hasNext, + }); + return res.data.items.map(toPostCard); + }); + + await fetch(getNotices); + }, + [initialQuery, fetch, filters, changeTimeFilter] + ); + + const reset = useCallback(() => { + setFiltersState(INIT_FILTER_DATA); + fetchNotices(INIT_FILTER_DATA); + }, []); + + return { + notices: data ?? [], + pagination, + isLoading, + isInitialized, + error, + fetchNotices, + filters, + reset, + }; +}; +export default useNotices; diff --git a/src/components/features/noticeList/hooks/useRecentNotice.ts b/src/components/features/noticeList/hooks/useRecentNotice.ts new file mode 100644 index 0000000..7fb2efc --- /dev/null +++ b/src/components/features/noticeList/hooks/useRecentNotice.ts @@ -0,0 +1,55 @@ +import type { NoticeCard, RecentNotice } from '@/types/notice'; +import { useCallback, useEffect, useState } from 'react'; + +const RECENT_KEY = 'thejulge_recent'; + +// 최근 본 공고 저장 +export const useRecentNotice = (notice: NoticeCard) => { + const handleRecentNotice = useCallback(() => { + const current: RecentNotice = { + id: notice.id, + shopId: notice.shopId, + name: notice.name, + address1: notice.address1, + imageUrl: notice.imageUrl, + hourlyPay: notice.hourlyPay, + startsAt: notice.startsAt, + workhour: notice.workhour, + closed: notice.closed, + originalHourlyPay: notice.originalHourlyPay, + viewedAt: new Date().toISOString(), + }; + + // 기존 데이터 가져오기 + const stored = localStorage.getItem(RECENT_KEY); + let recentList: RecentNotice[] = stored ? JSON.parse(stored) : []; + + // 중복 제거 같은 noticeId면 제거 + recentList = recentList.filter(item => item.id !== current.id); + + // 최신 항목 맨 앞에 추가 + recentList.unshift(current); + + // 최대 6개까지만 저장 + if (recentList.length > 6) recentList = recentList.slice(0, 6); + + localStorage.setItem(RECENT_KEY, JSON.stringify(recentList)); + }, [notice]); + + return { handleRecentNotice }; +}; + +// 최근 본 공고 불러오기 +export const useRecentNoticeList = () => { + const [recentNotices, setRecentNotices] = useState([]); + + useEffect(() => { + const stored = localStorage.getItem(RECENT_KEY); + if (stored) { + const parsed: RecentNotice[] = JSON.parse(stored); + setRecentNotices(parsed); + } + }, []); + + return { recentNotices }; +}; diff --git a/src/components/features/noticeList/index.ts b/src/components/features/noticeList/index.ts new file mode 100644 index 0000000..7f6a357 --- /dev/null +++ b/src/components/features/noticeList/index.ts @@ -0,0 +1,4 @@ +export { default as CustomNotice } from './customNotice'; +export { default as RecentNoticeList } from './recentNoticeList'; +export { default as NoticeListSection } from './noticeListSection'; +export { default as NoticeEmpty } from './noticeEmpty'; diff --git a/src/components/features/noticeList/noticeEmpty.tsx b/src/components/features/noticeList/noticeEmpty.tsx new file mode 100644 index 0000000..37fb264 --- /dev/null +++ b/src/components/features/noticeList/noticeEmpty.tsx @@ -0,0 +1,36 @@ +import { Button } from '@/components/ui'; +import { useRouter } from 'next/router'; + +interface NoticeEmptyProps { + q?: string; + onReset?: () => void; +} + +const NoticeEmpty = ({ q, onReset }: NoticeEmptyProps) => { + const router = useRouter(); + return ( +
    +

    + {q ? `"${q}"에 대한 공고를 찾을 수 없습니다.` : '검색 조건에 맞는 공고가 없습니다.'} +

    + {!q && ( +

    + 새로운 아르바이트 공고가 올라오면 이곳에서 확인할 수 있어요. +

    + )} + +
    + {q ? ( + + ) : ( + + )} +
    +
    + ); +}; +export default NoticeEmpty; diff --git a/src/components/features/noticeList/noticeList.tsx b/src/components/features/noticeList/noticeList.tsx new file mode 100644 index 0000000..5294dc2 --- /dev/null +++ b/src/components/features/noticeList/noticeList.tsx @@ -0,0 +1,33 @@ +import { Post, SkeletonUI } from '@/components/ui'; +import { ApiAsync } from '@/types/api'; +import { PostCard } from '@/types/notice'; +import NoticeEmpty from './noticeEmpty'; + +interface NoticeProps extends ApiAsync { + notices: PostCard[]; + q?: string; + reset: () => void; +} + +const NoticeList = ({ notices, q, isLoading, isInitialized, reset, error }: NoticeProps) => { + if (error) { + return
    {error}
    ; + } + if (!isInitialized || isLoading) { + if (q) return; + ; + } + + if (notices.length === 0) { + return reset()} />; + } + + return ( +
    + {notices.map(notice => ( + + ))} +
    + ); +}; +export default NoticeList; diff --git a/src/components/features/noticeList/noticeListFilter.tsx b/src/components/features/noticeList/noticeListFilter.tsx new file mode 100644 index 0000000..88b1eeb --- /dev/null +++ b/src/components/features/noticeList/noticeListFilter.tsx @@ -0,0 +1,52 @@ +import { Dropdown, Filter } from '@/components/ui'; +import { getActiveFilterCount } from '@/components/ui/filter/getActiveFilterCount'; +import { SORT_CODE, type SortCode } from '@/constants/dropdown'; +import { FilterQuery, NoticeQuery, type sort } from '@/types/api'; +import { useMemo } from 'react'; + +const SORT_TO_API: Record = { + '마감 임박 순': 'time', + '시급 많은 순': 'pay', + '시간 적은 순': 'hour', + '가나다 순': 'shop', +}; + +interface NoticeListFilterProps { + filters: NoticeQuery; + onSortChange: (sort: sort) => void; + onFilterSubmit: (filter: FilterQuery) => void; +} + +const NoticeListFilter = ({ filters, onSortChange, onFilterSubmit }: NoticeListFilterProps) => { + const selectedLabel = useMemo(() => { + const currentSort = filters.sort ?? 'time'; + const entry = Object.entries(SORT_TO_API).find(([, v]) => v === currentSort); + return entry?.[0] as SortCode; + }, [filters.sort]); + + const appliedCount = getActiveFilterCount(filters); + + const handleSort = (next: SortCode) => { + const s = SORT_TO_API[next]; + onSortChange(s); + }; + + const handleFilter = (filter: FilterQuery) => { + onFilterSubmit(filter); + }; + + return ( +
    + + +
    + ); +}; +export default NoticeListFilter; diff --git a/src/components/features/noticeList/noticeListHeader.tsx b/src/components/features/noticeList/noticeListHeader.tsx new file mode 100644 index 0000000..12dd831 --- /dev/null +++ b/src/components/features/noticeList/noticeListHeader.tsx @@ -0,0 +1,15 @@ +const NoticeListHeader = ({ q }: { q?: string }) => { + return ( +

    + {q ? ( + <> + {q}에 대한 공고 목록 + + ) : ( + '전체공고' + )} +

    + ); +}; + +export default NoticeListHeader; diff --git a/src/components/features/noticeList/noticeListSection.tsx b/src/components/features/noticeList/noticeListSection.tsx new file mode 100644 index 0000000..6d18a56 --- /dev/null +++ b/src/components/features/noticeList/noticeListSection.tsx @@ -0,0 +1,56 @@ +import NoticeList from '@/components/features/noticeList/noticeList'; +import NoticeListFilter from '@/components/features/noticeList/noticeListFilter'; +import { Container } from '@/components/layout'; +import { Pagination } from '@/components/ui'; +import type { NoticeQuery } from '@/types/api'; +import { useEffect } from 'react'; +import useNotices from './hooks/useNotices'; +import NoticeListHeader from './noticeListHeader'; + +interface NoticeListSectionProps { + q?: string; // 검색어 + initialFilters?: Partial; // 섹션 진입 시 적용할 초기 필터 +} + +const NoticeListSection = ({ q, initialFilters }: NoticeListSectionProps) => { + const { notices, isLoading, isInitialized, error, pagination, fetchNotices, reset, filters } = + useNotices(); + + useEffect(() => { + // 새 검색어/필터로 진입 시 페이지를 1페이지로 리셋 + fetchNotices({ ...(initialFilters ?? {}), offset: 0 }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [q, initialFilters]); + + return ( + +
    + + fetchNotices({ sort })} + onFilterSubmit={filter => fetchNotices(filter)} + /> +
    + + {!isLoading && ( + fetchNotices({ offset: next })} + className='mt-8 tablet:mt-10' + /> + )} +
    + ); +}; + +export default NoticeListSection; diff --git a/src/components/features/noticeList/recentNoticeList.tsx b/src/components/features/noticeList/recentNoticeList.tsx new file mode 100644 index 0000000..fbf9ecd --- /dev/null +++ b/src/components/features/noticeList/recentNoticeList.tsx @@ -0,0 +1,21 @@ +import { Container } from '@/components/layout'; +import { Post } from '@/components/ui'; +import { useRecentNoticeList } from './hooks/useRecentNotice'; + +const RecentNoticeList = () => { + const { recentNotices } = useRecentNoticeList(); + + if (recentNotices.length === 0) return null; + + return ( + +

    최근에 본 공고

    +
    + {recentNotices.map(notice => ( + + ))} +
    +
    + ); +}; +export default RecentNoticeList; diff --git a/src/components/layout/container/container.tsx b/src/components/layout/container/container.tsx new file mode 100644 index 0000000..3731977 --- /dev/null +++ b/src/components/layout/container/container.tsx @@ -0,0 +1,29 @@ +import { cn } from '@/lib/utils/cn'; +import { ElementType, ReactNode } from 'react'; + +interface Props { + as?: ElementType; + className?: string; + isPage?: boolean; + children: ReactNode; +} + +export const Wrapper = ({ children }: { children: ReactNode }) => { + return
    {children}
    ; +}; + +const Container = ({ as: Component = 'div', isPage = false, className, children }: Props) => { + return ( + + {children} + + ); +}; +export default Container; diff --git a/src/components/layout/container/index.ts b/src/components/layout/container/index.ts new file mode 100644 index 0000000..7a76bc1 --- /dev/null +++ b/src/components/layout/container/index.ts @@ -0,0 +1 @@ +export { default as Container, Wrapper } from './container'; diff --git a/src/components/layout/footer/footer.tsx b/src/components/layout/footer/footer.tsx new file mode 100644 index 0000000..3c06893 --- /dev/null +++ b/src/components/layout/footer/footer.tsx @@ -0,0 +1,45 @@ +import { Container } from '@/components/layout'; +import { Icon } from '@/components/ui'; +import Link from 'next/link'; + +const Footer = () => { + return ( +
    + +
    +
    + ©codeit - 2023 +
    +
    + + Privacy Policy + + + FAQ + +
    + +
      +
    • + + + +
    • +
    • + + + +
    • +
    • + + + +
    • +
    +
    +
    +
    + ); +}; + +export default Footer; diff --git a/src/components/layout/footer/index.ts b/src/components/layout/footer/index.ts new file mode 100644 index 0000000..13f355b --- /dev/null +++ b/src/components/layout/footer/index.ts @@ -0,0 +1 @@ +export { default as Footer } from './footer'; diff --git a/src/components/layout/frame/frame.tsx b/src/components/layout/frame/frame.tsx new file mode 100644 index 0000000..5c7d5f1 --- /dev/null +++ b/src/components/layout/frame/frame.tsx @@ -0,0 +1,30 @@ +import { Container } from '@/components/layout/container'; +import Button from '@/components/ui/button/button'; +import Link from 'next/link'; + +interface FrameProps { + title: string; + content: string; + buttonText: string; + href: string; +} + +const Frame = ({ title, content, buttonText, href }: FrameProps) => { + return ( + <> + +

    {title}

    +
    +

    {content}

    +
    + +
    +
    +
    + + ); +}; + +export default Frame; diff --git a/src/components/layout/frame/index.ts b/src/components/layout/frame/index.ts new file mode 100644 index 0000000..1a4475a --- /dev/null +++ b/src/components/layout/frame/index.ts @@ -0,0 +1 @@ +export { default as Frame } from './frame'; diff --git a/src/components/layout/header/header.tsx b/src/components/layout/header/header.tsx new file mode 100644 index 0000000..8662ac4 --- /dev/null +++ b/src/components/layout/header/header.tsx @@ -0,0 +1,22 @@ +import { Container } from '@/components/layout'; +import { cn } from '@/lib/utils/cn'; +import Logo from './logo'; +import Nav from './nav'; +import SearchBar from './searchBar'; +const Header = () => { + return ( + + + +