diff --git a/.github/actions/pnpm-install/action.yml b/.github/actions/pnpm-install/action.yml index a305a41f1..988227e3f 100644 --- a/.github/actions/pnpm-install/action.yml +++ b/.github/actions/pnpm-install/action.yml @@ -7,7 +7,7 @@ runs: - name: Setup pnpm uses: pnpm/action-setup@v2 with: - version: 8 # or your preferred version + version: 8 - name: Get pnpm store directory id: pnpm-cache diff --git a/.github/workflows/ci-app.yml b/.github/workflows/ci-app.yml deleted file mode 100644 index 1f31d00f7..000000000 --- a/.github/workflows/ci-app.yml +++ /dev/null @@ -1,67 +0,0 @@ -name: CI-APP -on: - pull_request: - paths: - - 'services/ahhachul.com/**' - - 'package.json' - - 'pnpm-lock.yaml' - - '.github/workflows/**' - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -env: - DEFAULT_NODE_VERSION: '20.13.0' - -jobs: - ci: - runs-on: ubuntu-latest - timeout-minutes: 15 - - steps: - - name: Check out the repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set up Node.js - uses: actions/setup-node@v3 - timeout-minutes: 1 - with: - node-version: ${{ env.DEFAULT_NODE_VERSION }} - - - name: Install dependencies - uses: ./.github/actions/pnpm-install - timeout-minutes: 5 - - - name: Create .env file for ahhachul.com - timeout-minutes: 1 - run: | - echo '${{ secrets.DEV_REACT_APP_CONFIG }}' > config.json - cat config.json | jq -r 'to_entries[] | "\(.key)=\(.value)"' >> services/ahhachul.com/.env - - - name: Lint ahhachul.com - timeout-minutes: 3 - run: pnpm lint:app - continue-on-error: false - - - name: Test ahhachul.com - if: success() - timeout-minutes: 5 - run: pnpm test:app - continue-on-error: false - - - name: Build ahhachul.com - if: success() - timeout-minutes: 5 - run: pnpm build:app - continue-on-error: false - - - name: Upload test results - if: always() - uses: actions/upload-artifact@v3 - with: - name: test-results - path: services/ahhachul.com/coverage - retention-days: 7 diff --git a/.github/workflows/ci-one-app.yml b/.github/workflows/ci-one-app.yml deleted file mode 100644 index f5e0b11d3..000000000 --- a/.github/workflows/ci-one-app.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: CI-ONE-APP -on: - pull_request: - paths: - - 'services/one-app/**' - - 'package.json' - - 'pnpm-lock.yaml' - - '.github/workflows/**' - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -env: - DEFAULT_NODE_VERSION: '20.13.0' - -jobs: - ci: - runs-on: ubuntu-latest - timeout-minutes: 15 - - steps: - - name: Check out the repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set up Node.js - uses: actions/setup-node@v3 - timeout-minutes: 1 - with: - node-version: ${{ env.DEFAULT_NODE_VERSION }} - - - name: Install dependencies - uses: ./.github/actions/pnpm-install - timeout-minutes: 5 - - - name: Create .env file for one-app - timeout-minutes: 1 - run: | - echo '${{ secrets.DEV_ONE_APP_CONFIG }}' | jq -r 'to_entries | .[] | "\(.key)=\(.value)"' > services/one-app/.env - - - name: TypeScript Compilation Check - timeout-minutes: 3 - working-directory: ./services/one-app - run: pnpm tsc --noEmit --project tsconfig.json - continue-on-error: false - - - name: Lint one-app - timeout-minutes: 3 - run: pnpm lint:one-app - continue-on-error: false - - - name: Test one-app - if: success() - timeout-minutes: 5 - run: pnpm test:one-app - continue-on-error: false - - - name: Build one-app - if: success() - timeout-minutes: 5 - run: pnpm build:one-app - continue-on-error: false - - - name: Upload test results - if: always() - uses: actions/upload-artifact@v3 - with: - name: one-app-test-results - path: services/one-app/coverage - retention-days: 7 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..41f5b1bcc --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,58 @@ +name: APP CI + +on: + pull_request: + paths: + - 'services/**' + - 'package.json' + - 'pnpm-lock.yaml' + - '.github/workflows/**' + +permissions: + actions: read + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + DEFAULT_NODE_VERSION: '20.13.0' + HUSKY: '0' + NEXT_TELEMETRY_DISABLED: '1' + NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }} + +jobs: + main: + name: ๐Ÿš€ CI Pipeline + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: ๐Ÿ“ฅ Checkout Repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: ๐Ÿ”จ Setup Node.js + uses: actions/setup-node@v3 + timeout-minutes: 1 + with: + node-version: ${{ env.DEFAULT_NODE_VERSION }} + + - name: ๐Ÿ“ฆ Install Dependencies + uses: ./.github/actions/pnpm-install + timeout-minutes: 5 + + - name: ๐Ÿ”„ Set Nx Base/Head + uses: nrwl/nx-set-shas@v4 + + - name: ๐Ÿ”‘ Create Environment File + timeout-minutes: 1 + run: | + echo '${{ secrets.DEV_REACT_APP_CONFIG }}' > config.json + echo '${{ secrets.DEV_ONE_APP_CONFIG }}' | jq -r 'to_entries | .[] | "\(.key)=\(.value)"' > services/one-app/.env + + - name: ๐Ÿƒ Run CI Tasks + run: | + pnpm nx affected --parallel=3 -t type-check,build,test,lint diff --git a/.github/workflows/deploy-app.yml b/.github/workflows/deploy-app.yml index cb88c05e0..7c10aa9c5 100644 --- a/.github/workflows/deploy-app.yml +++ b/.github/workflows/deploy-app.yml @@ -4,7 +4,9 @@ on: push: branches: - main + - develop paths: + - 'packages/**' - 'services/ahhachul.com/**' env: @@ -20,11 +22,6 @@ jobs: with: fetch-depth: 0 - - name: Parse secrets from JSON - id: secrets - run: | - echo '${{ secrets.DEV_META_DATA }}' | jq -r 'to_entries | .[] | "echo \(.key)=\(.value) >> $GITHUB_ENV"' | bash - - name: Set up Node.js uses: actions/setup-node@v3 with: @@ -33,13 +30,20 @@ jobs: - name: Install dependencies uses: ./.github/actions/pnpm-install + - name: Set AWS Environment Variables + run: | + echo "AWS_ACCESS_KEY_ID=${{ fromJson(secrets.DEV_META_DATA).AWS_ACCESS_KEY_ID }}" >> $GITHUB_ENV + echo "AWS_SECRET_ACCESS_KEY=${{ fromJson(secrets.DEV_META_DATA).AWS_SECRET_ACCESS_KEY }}" >> $GITHUB_ENV + echo "AWS_REGION=${{ fromJson(secrets.DEV_META_DATA).AWS_REGION }}" >> $GITHUB_ENV + echo "S3_BUCKET_NAME=${{ fromJson(secrets.DEV_META_DATA).S3_BUCKET_NAME }}" >> $GITHUB_ENV + echo "AWS_CLOUDFRONT_DISTRIBUTION_ID_USER=${{ fromJson(secrets.DEV_META_DATA).AWS_CLOUDFRONT_DISTRIBUTION_ID_USER }}" >> $GITHUB_ENV + - name: Create .env file for ahhachul.com run: | - echo '${{ secrets.DEV_REACT_APP_CONFIG }}' > config.json - cat config.json | jq -r 'to_entries[] | "\(.key)=\(.value)"' >> services/ahhachul.com/.env + echo '${{ secrets.DEV_REACT_APP_CONFIG }}' | jq -r 'to_entries[] | "\(.key)=\(.value)"' > services/ahhachul.com/.env - name: Build ahhachul.com - run: pnpm build:app + run: pnpm app:build - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v2 diff --git a/.github/workflows/deploy-one-app.yml b/.github/workflows/deploy-nextjs.yml similarity index 89% rename from .github/workflows/deploy-one-app.yml rename to .github/workflows/deploy-nextjs.yml index d02e9bddb..9f7742490 100644 --- a/.github/workflows/deploy-one-app.yml +++ b/.github/workflows/deploy-nextjs.yml @@ -4,7 +4,10 @@ on: push: branches: - main + # ์ถ”ํ›„ develop ๋ธŒ๋ Œ์น˜๋Š” ์ œ์™ธ + - develop paths: + - 'packages/**' - 'services/one-app/**' env: @@ -33,6 +36,14 @@ jobs: with: node-version: ${{ env.DEFAULT_NODE_VERSION }} + - name: Update Corepack + run: | + echo "Before: corepack version => $(corepack --version || echo 'not installed')" + npm install -g corepack@latest + echo "After : corepack version => $(corepack --version)" + corepack enable + pnpm --version + - name: Install dependencies uses: ./.github/actions/pnpm-install diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..25fa6215f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "typescript.tsdk": "node_modules/typescript/lib" +} diff --git a/Dockerfile b/Dockerfile index a760ab1b0..ebba48241 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,20 +1,29 @@ -FROM node:20-alpine +FROM node:20.13.0-alpine + +RUN npm install -g corepack@latest && \ + corepack enable && \ + corepack prepare pnpm@latest --activate + RUN apk add --no-cache libc6-compat + WORKDIR /app -COPY services/one-app ./services/one-app -COPY nx.json tsconfig* ./ -COPY package.json ./ -COPY pnpm-lock.yaml ./ -COPY pnpm-workspace.yaml ./ -COPY .nx ./ +COPY packages/utils ./packages/utils +COPY services/one-app ./services/one-app +COPY nx.json tsconfig* ./ +COPY package.json ./ +COPY pnpm-lock.yaml ./ +COPY pnpm-workspace.yaml ./ +COPY .nx ./ -RUN pnpm install +RUN pnpm install && \ + cd services/one-app && \ + pnpm install sharp ENV NEXT_TELEMETRY_DISABLED=1 -RUN pnpm build:one-app +RUN pnpm nextjs:build EXPOSE 3000 -CMD ["pnpm", "start:one-app"] +CMD ["pnpm", "nextjs:start"] \ No newline at end of file diff --git a/nx.json b/nx.json index 18af629a9..f3df7a6c0 100644 --- a/nx.json +++ b/nx.json @@ -1,78 +1,64 @@ { "extends": "nx/presets/npm.json", - "affected": { - "defaultBase": "main" - }, - "workspaceLayout": { - "appsDir": "services", - "libsDir": "packages" - }, - "tasksRunnerOptions": { - "default": { - "runner": "nx/tasks-runners/default", - "options": { - "cacheableOperations": ["build", "test", "test:run", "test:coverage", "lint", "lint:fix"] - } - } - }, + "workspaceLayout": { "appsDir": "services", "libsDir": "packages" }, "targetDefaults": { - "dev": { - "cache": false, - "persistent": true - }, - "build": { - "dependsOn": ["^build"], - "outputs": ["{projectRoot}/dist/**"] - }, - "start": { - "dependsOn": ["build"], - "cache": false, - "persistent": true + "type-check": { + "cache": true, + "inputs": [ + "{projectRoot}/**/*.{ts,tsx}", + "{projectRoot}/tsconfig.json", + "{workspaceRoot}/tsconfig.base.json" + ] }, "lint": { - "dependsOn": ["^lint"], - "inputs": ["{projectRoot}/**/*.{js,ts,tsx}"] - }, - "lint:fix": { - "dependsOn": ["^lint:fix"], - "inputs": ["{projectRoot}/**/*.{js,ts,tsx}"] - }, - "test": { - "dependsOn": ["^build"], - "cache": false, - "inputs": ["{projectRoot}/src/**/*.{js,ts,tsx}", "{projectRoot}/test/**/*.{js,ts,tsx}"] - }, - "test:run": { - "dependsOn": ["^build"], "cache": true, - "inputs": ["{projectRoot}/src/**/*.{js,ts,tsx}", "{projectRoot}/test/**/*.{js,ts,tsx}"] + "inputs": [ + "{projectRoot}/**/*.{js,ts,tsx}", + "{projectRoot}/.eslintrc.*", + "{workspaceRoot}/.eslintrc.*" + ] }, - "test:coverage": { - "dependsOn": ["^build"], + "build": { "cache": true, - "inputs": ["{projectRoot}/src/**/*.{js,ts,tsx}", "{projectRoot}/test/**/*.{js,ts,tsx}"], - "outputs": ["{projectRoot}/coverage/**"] - }, - "test:playwright": { "dependsOn": ["^build"], - "cache": false, - "inputs": ["{projectRoot}/src/**/*.{js,ts,tsx}", "{projectRoot}/e2e/**/*.{js,ts,tsx}"] + "inputs": ["production", "{workspaceRoot}/package.json"], + "outputs": ["{projectRoot}/dist/**", "{projectRoot}/.next/**"] }, - "test:playwright:ui": { - "dependsOn": ["^build"], - "cache": false, - "inputs": ["{projectRoot}/src/**/*.{js,ts,tsx}", "{projectRoot}/e2e/**/*.{js,ts,tsx}"] + "test": { + "cache": true, + "inputs": [ + "{projectRoot}/src/**/*.{js,ts,tsx}", + "{projectRoot}/test/**/*.{js,ts,tsx}", + "{projectRoot}/jest.config.*", + "{workspaceRoot}/jest.config.*" + ] }, - "test:all": { - "dependsOn": ["^build", "test:run", "test:playwright"], - "cache": false + "start": { "cache": false, "persistent": true, "dependsOn": ["^build"] }, + "lint:fix": { + "cache": true, + "inputs": [ + "{projectRoot}/**/*.{js,ts,tsx}", + "{projectRoot}/.eslintrc.*", + "{workspaceRoot}/.eslintrc.*" + ] } }, + "defaultBase": "main", "namedInputs": { - "default": ["{projectRoot}/**/*", "!{projectRoot}/**/*.md"], - "production": ["default", "!{projectRoot}/**/*.spec.ts"] + "default": [ + "{projectRoot}/**/*", + "!{projectRoot}/**/*.md", + "!{projectRoot}/**/CHANGELOG.md", + "!{projectRoot}/**/*.spec.ts", + "!{projectRoot}/coverage/**" + ], + "production": [ + "default", + "!{projectRoot}/**/*.spec.ts", + "!{projectRoot}/test/**/*", + "!{projectRoot}/**/__tests__/**/*" + ] }, - "implicitDependencies": { - "**/.env.*local": "*" - } + "parallel": 3, + "nxCloudId": "67a199375e4612c72a76ed4f" } diff --git a/package.json b/package.json index c66f4eb5e..804221ec6 100644 --- a/package.json +++ b/package.json @@ -1,27 +1,25 @@ { - "name": "ahhachul-front", + "name": "ahhachul", "private": true, "scripts": { - "clean:branch": "git fetch --prune && git branch | grep -v '^*' | xargs git branch -D", - "clean:cache": "nx reset", "prepare": "husky", "preinstall": "corepack enable", - "start": "nx run-many --target=start --all", - "lint": "nx run-many --target=lint --all", - "lint:fix": "nx run-many --target=lint:fix --all", - "test": "nx run-many --target=test --all", - "test:run": "nx run-many --target=test:run --all", - "test:coverage": "nx run-many --target=test:coverage --all", - "test:e2e": "nx run-many --target=test:playwright --all", - "test:e2e:ui": "nx run-many --target=test:playwright:ui --all", - "build:all": "nx run-many --target=build --all", - "lint:app": "nx lint @ahhachul/app", - "test:app": "nx test @ahhachul/app", - "build:app": "nx build @ahhachul/app", - "lint:one-app": "nx lint @ahhachul/one-app", - "test:one-app": "nx test @ahhachul/one-app", - "build:one-app": "nx build @ahhachul/one-app", - "start:one-app": "nx start @ahhachul/one-app" + "clean:branch": "git fetch --prune && git branch | grep -v '^*' | xargs git branch -D", + "clean:cache": "nx reset", + "app:type": "nx type-check @ahhachul/app", + "app:lint": "nx lint @ahhachul/app", + "app:test": "nx test @ahhachul/app", + "app:build": "nx build @ahhachul/app", + "nextjs:type": "nx type-check @ahhachul/one-app", + "nextjs:lint": "nx lint @ahhachul/one-app", + "nextjs:test": "nx test @ahhachul/one-app", + "nextjs:build": "nx build @ahhachul/one-app", + "nextjs:start": "nx start @ahhachul/one-app", + "all:start": "nx run-many --target=start --all", + "all:build": "nx run-many --target=build --all", + "all:lint": "nx run-many --target=lint --all", + "all:lint:fix": "nx run-many --target=lint:fix --all", + "all:test": "nx run-many --target=test --all" }, "devDependencies": { "@trivago/prettier-plugin-sort-imports": "^4.3.0", @@ -37,7 +35,7 @@ "nano-staged": "^0.8.0", "nx": "^20.3.1", "prettier": "^3.2.5", - "typescript": "5.4.5" + "typescript": "^5" }, "packageManager": "pnpm@9.1.0", "engines": { diff --git a/packages/utils/package.json b/packages/utils/package.json index 7441d57ca..15f4b9028 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -18,6 +18,7 @@ "date-fns": "^4.1.0" }, "devDependencies": { - "@types/crypto-js": "^4.2.2" + "@types/crypto-js": "^4.2.2", + "typescript": "^5" } } diff --git a/services/one-app/src/common/utils/array.ts b/packages/utils/src/array.ts similarity index 100% rename from services/one-app/src/common/utils/array.ts rename to packages/utils/src/array.ts diff --git a/services/one-app/src/common/utils/common.ts b/packages/utils/src/delay.ts similarity index 100% rename from services/one-app/src/common/utils/common.ts rename to packages/utils/src/delay.ts diff --git a/services/ahhachul.com/src/utils/form.ts b/packages/utils/src/form.ts similarity index 88% rename from services/ahhachul.com/src/utils/form.ts rename to packages/utils/src/form.ts index 7498d15f0..fc32118ac 100644 --- a/services/ahhachul.com/src/utils/form.ts +++ b/packages/utils/src/form.ts @@ -1,5 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ - export const appendFilesToFormData = (formData: FormData, files: File[], name = 'files'): void => { files.forEach(file => { formData.append(name, file, file.name); @@ -16,6 +14,7 @@ export const extractFormData = ( form: T, excludeKey: K, ): Omit => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars const { [excludeKey]: _, ...restData } = form; return restData; }; diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 5c2a163f0..dd92de197 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -1,4 +1,7 @@ export * from './date'; +export * from './form'; +export * from './delay'; +export * from './array'; export * from './number'; export * from './object'; export * from './crypto'; diff --git a/packages/utils/src/object.ts b/packages/utils/src/object.ts index 6be9e40af..6f88441c1 100644 --- a/packages/utils/src/object.ts +++ b/packages/utils/src/object.ts @@ -1,4 +1,5 @@ -import type { ObjectKeys } from './object.type'; +export type ObjectQueryParams = Record; +export type ObjectKeys> = `${Exclude}`; export const isValidObject = (obj: unknown): obj is Record => { return typeof obj === 'object' && obj !== null && !Array.isArray(obj); @@ -25,8 +26,34 @@ export function removeFalsyValues>( }, {} as Partial); } +export function objectKeys>( + obj: Type, +): Array> { + return Object.keys(obj) as Array>; +} + export function objectEntries>( obj: Type, ): Array<[ObjectKeys, Type[ObjectKeys]]> { return Object.entries(obj) as Array<[ObjectKeys, Type[ObjectKeys]]>; } + +export const objectToQueryString = (params: ObjectQueryParams): string => { + return Object.entries(params) + .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`) + .join('&'); +}; + +export const queryStringToObject = (queryString: string): Record => { + const query = queryString.startsWith('?') ? queryString.slice(1) : queryString; + + if (!query) return {}; + + return query.split('&').reduce((params: Record, param) => { + const [key, value] = param.split('=').map(decodeURIComponent); + if (key) { + params[key] = value || ''; + } + return params; + }, {}); +}; diff --git a/packages/utils/src/object.type.ts b/packages/utils/src/object.type.ts deleted file mode 100644 index d513115c4..000000000 --- a/packages/utils/src/object.type.ts +++ /dev/null @@ -1 +0,0 @@ -export type ObjectKeys> = `${Exclude}`; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6a0f96da2..11e2e502c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,10 +12,10 @@ importers: version: 4.3.0(prettier@3.4.2) '@typescript-eslint/eslint-plugin': specifier: ^7.10.0 - version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.4.5))(eslint@8.57.1)(typescript@5.4.5) + version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3) '@typescript-eslint/parser': specifier: ^7.10.0 - version: 7.18.0(eslint@8.57.1)(typescript@5.4.5) + version: 7.18.0(eslint@8.57.1)(typescript@5.7.3) eslint: specifier: ^8.56.0 version: 8.57.1 @@ -27,13 +27,13 @@ importers: version: 6.0.2(eslint@8.57.1) eslint-plugin-prettier: specifier: ^5.1.3 - version: 5.2.1(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@8.57.1))(eslint@8.57.1)(prettier@3.4.2) + version: 5.2.3(eslint-config-prettier@9.1.0(eslint@8.57.1))(eslint@8.57.1)(prettier@3.4.2) eslint-plugin-react: specifier: ^7.34.1 version: 7.37.4(eslint@8.57.1) eslint-plugin-unused-imports: specifier: ^4.1.4 - version: 4.1.4(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.4.5))(eslint@8.57.1)(typescript@5.4.5))(eslint@8.57.1) + version: 4.1.4(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1) husky: specifier: ^9.0.11 version: 9.1.7 @@ -42,13 +42,13 @@ importers: version: 0.8.0 nx: specifier: ^20.3.1 - version: 20.3.1(@swc/core@1.10.7) + version: 20.3.3(@swc/core@1.10.11) prettier: specifier: ^3.2.5 version: 3.4.2 typescript: - specifier: 5.4.5 - version: 5.4.5 + specifier: ^5 + version: 5.7.3 packages/esbuild-config: dependencies: @@ -68,51 +68,60 @@ importers: '@types/crypto-js': specifier: ^4.2.2 version: 4.2.2 + typescript: + specifier: ^5 + version: 5.7.3 services/ahhachul.com: dependencies: '@emotion/react': specifier: ^11.14.0 - version: 11.14.0(@types/react@18.3.18)(react@18.3.1) + version: 11.14.0(@types/react@19.0.8)(react@19.0.0) '@emotion/styled': specifier: ^11.14.0 - version: 11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) + version: 11.14.0(@emotion/react@11.14.0(@types/react@19.0.8)(react@19.0.0))(@types/react@19.0.8)(react@19.0.0) '@hookform/resolvers': specifier: ^3.9.1 - version: 3.10.0(react-hook-form@7.54.2(react@18.3.1)) + version: 3.10.0(react-hook-form@7.54.2(react@19.0.0)) + '@lexical/link': + specifier: ^0.27.1 + version: 0.27.1 '@lexical/react': specifier: ^0.23.1 - version: 0.23.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(yjs@13.6.22) + version: 0.23.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(yjs@13.6.23) '@lottiefiles/react-lottie-player': specifier: ^3.6.0 - version: 3.6.0(react@18.3.1) + version: 3.6.0(react@19.0.0) '@radix-ui/react-dropdown-menu': specifier: ^2.1.4 - version: 2.1.4(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 2.1.5(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-icons': + specifier: ^1.3.2 + version: 1.3.2(react@19.0.0) '@stackflow/core': specifier: ^1.0.10 - version: 1.1.0 + version: 1.1.1 '@stackflow/link': specifier: ^1.4.0 - version: 1.6.0(@stackflow/core@1.1.0)(@stackflow/plugin-history-sync@1.7.0(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@stackflow/react@1.4.1(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@stackflow/plugin-preload@1.4.3(@stackflow/core@1.1.0)(@stackflow/plugin-history-sync@1.7.0(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@stackflow/react@1.4.1(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@stackflow/react@1.4.1(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@stackflow/react@1.4.1(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) + version: 1.6.1(@stackflow/core@1.1.1)(@stackflow/plugin-history-sync@1.7.1(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@stackflow/react@1.4.2(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@types/react@19.0.8)(react@19.0.0))(@types/react@19.0.8)(react@19.0.0))(@stackflow/plugin-preload@1.4.3(@stackflow/core@1.1.1)(@stackflow/plugin-history-sync@1.7.1(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@stackflow/react@1.4.2(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@types/react@19.0.8)(react@19.0.0))(@types/react@19.0.8)(react@19.0.0))(@stackflow/react@1.4.2(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@types/react@19.0.8)(react@19.0.0))(@types/react@19.0.8)(react@19.0.0))(@stackflow/react@1.4.2(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@types/react@19.0.8)(react@19.0.0))(@types/react@19.0.8)(react@19.0.0) '@stackflow/plugin-basic-ui': specifier: ^1.5.3 - version: 1.11.1(@stackflow/core@1.1.0)(@stackflow/react@1.4.1(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(babel-plugin-macros@3.1.0)(react@18.3.1) + version: 1.11.1(@stackflow/core@1.1.1)(@stackflow/react@1.4.2(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@types/react@19.0.8)(react@19.0.0))(@types/react@19.0.8)(babel-plugin-macros@3.1.0)(react@19.0.0) '@stackflow/plugin-history-sync': specifier: ^1.4.0 - version: 1.7.0(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@stackflow/react@1.4.1(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) + version: 1.7.1(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@stackflow/react@1.4.2(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@types/react@19.0.8)(react@19.0.0))(@types/react@19.0.8)(react@19.0.0) '@stackflow/plugin-preload': specifier: ^1.4.1 - version: 1.4.3(@stackflow/core@1.1.0)(@stackflow/plugin-history-sync@1.7.0(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@stackflow/react@1.4.1(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@stackflow/react@1.4.1(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) + version: 1.4.3(@stackflow/core@1.1.1)(@stackflow/plugin-history-sync@1.7.1(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@stackflow/react@1.4.2(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@types/react@19.0.8)(react@19.0.0))(@types/react@19.0.8)(react@19.0.0))(@stackflow/react@1.4.2(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@types/react@19.0.8)(react@19.0.0))(@types/react@19.0.8)(react@19.0.0) '@stackflow/plugin-renderer-basic': specifier: ^1.1.8 - version: 1.1.13(@stackflow/core@1.1.0)(@stackflow/react@1.4.1(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) + version: 1.1.13(@stackflow/core@1.1.1)(@stackflow/react@1.4.2(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@types/react@19.0.8)(react@19.0.0))(@types/react@19.0.8)(react@19.0.0) '@stackflow/react': specifier: ^1.1.8 - version: 1.4.1(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@types/react@18.3.18)(react@18.3.1) + version: 1.4.2(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@types/react@19.0.8)(react@19.0.0) '@tanstack/react-query': specifier: ^5.62.8 - version: 5.64.1(react@18.3.1) + version: 5.64.2(react@19.0.0) axios: specifier: ^1.7.9 version: 1.7.9 @@ -122,142 +131,169 @@ importers: lexical: specifier: ^0.23.1 version: 0.23.1 + lucide-react: + specifier: ^0.460.0 + version: 0.460.0(react@19.0.0) motion: specifier: ^11.18.0 - version: 11.18.0(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 11.18.2(@emotion/is-prop-valid@1.3.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.0.0 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.0.0(react@19.0.0) react-hook-form: specifier: ^7.49.3 - version: 7.54.2(react@18.3.1) + version: 7.54.2(react@19.0.0) react-intersection-observer: specifier: ^9.15.0 - version: 9.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 9.15.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react-lazy-load-image-component: specifier: ^1.6.3 - version: 1.6.3(react@18.3.1) + version: 1.6.3(react@19.0.0) react-loading-skeleton: specifier: ^3.5.0 - version: 3.5.0(react@18.3.1) + version: 3.5.0(react@19.0.0) react-swipeable: specifier: ^7.0.2 - version: 7.0.2(react@18.3.1) + version: 7.0.2(react@19.0.0) + react-use-measure: + specifier: ^2.1.7 + version: 2.1.7(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react-zoom-pan-pinch: specifier: 3.6.0 - version: 3.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 3.6.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) rxjs: specifier: ^7.8.1 version: 7.8.1 swiper: specifier: ^11.1.15 version: 11.2.1 + uuid: + specifier: ^11.1.0 + version: 11.1.0 vaul: specifier: ^1.1.2 - version: 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - zod: - specifier: ^3.24.1 - version: 3.24.1 + version: 1.1.2(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) zustand: specifier: ^5.0.2 - version: 5.0.3(@types/react@18.3.18)(immer@9.0.21)(react@18.3.1)(use-sync-external-store@1.4.0(react@18.3.1)) + version: 5.0.3(@types/react@19.0.8)(react@19.0.0) devDependencies: '@ahhachul/utils': specifier: workspace:* version: link:../../packages/utils '@playwright/test': specifier: ^1.49.1 - version: 1.49.1 + version: 1.50.0 + '@svgr/plugin-svgo': + specifier: ^8.1.0 + version: 8.1.0(@svgr/core@8.1.0(typescript@5.7.3))(typescript@5.7.3) '@testing-library/jest-dom': specifier: ^6.6.3 version: 6.6.3 '@testing-library/react': specifier: ^16.1.0 - version: 16.1.0(@testing-library/dom@10.4.0)(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 16.2.0(@testing-library/dom@10.4.0)(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@testing-library/react-hooks': specifier: ^8.0.1 - version: 8.0.1(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 8.0.1(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@testing-library/user-event': specifier: ^14.5.2 - version: 14.5.2(@testing-library/dom@10.4.0) + version: 14.6.1(@testing-library/dom@10.4.0) '@types/js-cookie': specifier: ^3.0.6 version: 3.0.6 '@types/node': - specifier: 20.13.0 + specifier: ^20 version: 20.13.0 '@types/react': - specifier: ^18.3.12 - version: 18.3.18 + specifier: ^19 + version: 19.0.8 '@types/react-dom': - specifier: ^18.3.1 - version: 18.3.5(@types/react@18.3.18) + specifier: ^19 + version: 19.0.3(@types/react@19.0.8) '@types/react-lazy-load-image-component': specifier: ^1.6.4 version: 1.6.4 '@vitejs/plugin-react-swc': specifier: ^3.7.2 - version: 3.7.2(@swc/helpers@0.5.15)(vite@6.0.7(@types/node@20.13.0)(jiti@1.21.7)(terser@5.37.0)(yaml@2.7.0)) + version: 3.7.2(@swc/helpers@0.5.15)(vite@6.0.11(@types/node@20.13.0)(jiti@1.21.7)(yaml@2.7.0)) '@vitest/coverage-v8': specifier: ^2.1.8 - version: 2.1.8(vitest@2.1.8(@types/node@20.13.0)(@vitest/ui@2.1.8)(happy-dom@12.10.3)(jsdom@26.0.0)(msw@2.7.0(@types/node@20.13.0)(typescript@5.4.5))(terser@5.37.0)) + version: 2.1.8(vitest@2.1.8(@types/node@20.13.0)(@vitest/ui@2.1.8)(jsdom@26.0.0)(msw@2.7.0(@types/node@20.13.0)(typescript@5.7.3))) '@vitest/ui': specifier: ^2.1.8 version: 2.1.8(vitest@2.1.8) + autocannon: + specifier: ^8.0.0 + version: 8.0.0 globals: specifier: ^15.12.0 version: 15.14.0 jsdom: specifier: ^26.0.0 version: 26.0.0 + lighthouse: + specifier: ^12.3.0 + version: 12.3.0 msw: specifier: ^2.7.0 - version: 2.7.0(@types/node@20.13.0)(typescript@5.4.5) + version: 2.7.0(@types/node@20.13.0)(typescript@5.7.3) + puppeteer: + specifier: ^24.1.1 + version: 24.1.1(typescript@5.7.3) + rollup-plugin-visualizer: + specifier: ^5.14.0 + version: 5.14.0(rollup@4.32.0) + sharp: + specifier: ^0.33.5 + version: 0.33.5 typescript: - specifier: 5.4.5 - version: 5.4.5 + specifier: ^5 + version: 5.7.3 vite: specifier: ^6.0.1 - version: 6.0.7(@types/node@20.13.0)(jiti@1.21.7)(terser@5.37.0)(yaml@2.7.0) + version: 6.0.11(@types/node@20.13.0)(jiti@1.21.7)(yaml@2.7.0) + vite-bundle-analyzer: + specifier: ^0.16.1 + version: 0.16.1 + vite-plugin-image-optimizer: + specifier: ^1.1.8 + version: 1.1.8(vite@6.0.11(@types/node@20.13.0)(jiti@1.21.7)(yaml@2.7.0)) vite-plugin-svgr: specifier: ^4.3.0 - version: 4.3.0(rollup@4.30.1)(typescript@5.4.5)(vite@6.0.7(@types/node@20.13.0)(jiti@1.21.7)(terser@5.37.0)(yaml@2.7.0)) + version: 4.3.0(rollup@4.32.0)(typescript@5.7.3)(vite@6.0.11(@types/node@20.13.0)(jiti@1.21.7)(yaml@2.7.0)) vitest: specifier: ^2.1.8 - version: 2.1.8(@types/node@20.13.0)(@vitest/ui@2.1.8)(happy-dom@12.10.3)(jsdom@26.0.0)(msw@2.7.0(@types/node@20.13.0)(typescript@5.4.5))(terser@5.37.0) + version: 2.1.8(@types/node@20.13.0)(@vitest/ui@2.1.8)(jsdom@26.0.0)(msw@2.7.0(@types/node@20.13.0)(typescript@5.7.3)) services/one-app: dependencies: '@lexical/react': specifier: ^0.21.0 - version: 0.21.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(yjs@13.6.22) + version: 0.21.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(yjs@13.6.23) '@lottiefiles/react-lottie-player': specifier: ^3.5.4 - version: 3.5.4(react@18.3.1) + version: 3.6.0(react@19.0.0) '@radix-ui/react-dialog': specifier: ^1.1.2 - version: 1.1.4(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 1.1.5(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@radix-ui/react-dropdown-menu': specifier: ^2.1.2 - version: 2.1.4(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 2.1.5(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@radix-ui/react-slot': specifier: ^1.1.1 - version: 1.1.1(@types/react@18.3.18)(react@18.3.1) + version: 1.1.1(@types/react@19.0.8)(react@19.0.0) '@tanstack/react-query': - specifier: ^5.59.16 - version: 5.64.1(react@18.3.1) - axios: - specifier: ^1.7.7 - version: 1.7.9 + specifier: ^5.62.8 + version: 5.64.2(react@19.0.0) date-fns: specifier: ^4.1.0 version: 4.1.0 embla-carousel-react: specifier: ^8.5.1 - version: 8.5.2(react@18.3.1) + version: 8.5.2(react@19.0.0) js-cookie: specifier: ^3.0.5 version: 3.0.5 @@ -266,59 +302,65 @@ importers: version: 0.21.0 lucide-react: specifier: ^0.460.0 - version: 0.460.0(react@18.3.1) + version: 0.460.0(react@19.0.0) next: - specifier: 15.1.4 - version: 15.1.4(@babel/core@7.26.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - nuqs: - specifier: ^2.2.2 - version: 2.3.0(next@15.1.4(@babel/core@7.26.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router-dom@7.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@7.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) - query-string: - specifier: ^9.1.1 - version: 9.1.1 + specifier: 15.1.6 + version: 15.1.6(@babel/core@7.26.7)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + nextjs-toploader: + specifier: ^3.7.15 + version: 3.7.15(next@15.1.6(@babel/core@7.26.7)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.0.0 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.0.0(react@19.0.0) + react-hook-form: + specifier: ^7.49.3 + version: 7.54.2(react@19.0.0) react-intersection-observer: - specifier: ^9.13.1 - version: 9.14.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^9.15.0 + version: 9.15.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react-lazy-load-image-component: specifier: ^1.6.3 - version: 1.6.3(react@18.3.1) + version: 1.6.3(react@19.0.0) react-loading-skeleton: specifier: ^3.5.0 - version: 3.5.0(react@18.3.1) + version: 3.5.0(react@19.0.0) + react-swipeable: + specifier: ^7.0.2 + version: 7.0.2(react@19.0.0) react-zoom-pan-pinch: specifier: 3.6.0 - version: 3.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 3.6.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) swiper: specifier: ^11.1.15 version: 11.2.1 vaul: specifier: ^1.1.1 - version: 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 1.1.2(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) zod: specifier: ^3.23.8 version: 3.24.1 zustand: - specifier: ^5.0.0 - version: 5.0.3(@types/react@18.3.18)(immer@9.0.21)(react@18.3.1)(use-sync-external-store@1.4.0(react@18.3.1)) + specifier: ^5.0.2 + version: 5.0.3(@types/react@19.0.8)(react@19.0.0) devDependencies: + '@ahhachul/utils': + specifier: workspace:* + version: link:../../packages/utils '@faker-js/faker': specifier: ^9.0.3 - version: 9.3.0 - '@mswjs/http-middleware': - specifier: ^0.10.2 - version: 0.10.3(msw@2.7.0(@types/node@20.13.0)(typescript@5.4.5)) + version: 9.4.0 + '@next/bundle-analyzer': + specifier: ^15.1.6 + version: 15.1.6 '@svgr/webpack': specifier: ^8.1.0 - version: 8.1.0(typescript@5.4.5) + version: 8.1.0(typescript@5.7.3) '@tanstack/react-query-devtools': specifier: ^5.59.16 - version: 5.64.1(@tanstack/react-query@5.64.1(react@18.3.1))(react@18.3.1) + version: 5.64.2(@tanstack/react-query@5.64.2(react@19.0.0))(react@19.0.0) '@testing-library/dom': specifier: ^10.4.0 version: 10.4.0 @@ -327,16 +369,10 @@ importers: version: 6.6.3 '@testing-library/react': specifier: ^16.0.1 - version: 16.1.0(@testing-library/dom@10.4.0)(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 16.2.0(@testing-library/dom@10.4.0)(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@testing-library/user-event': specifier: ^14.5.2 - version: 14.5.2(@testing-library/dom@10.4.0) - '@types/cors': - specifier: ^2.8.17 - version: 2.8.17 - '@types/express': - specifier: ^5.0.0 - version: 5.0.0 + version: 14.6.1(@testing-library/dom@10.4.0) '@types/jest': specifier: ^29.5.14 version: 29.5.14 @@ -344,59 +380,56 @@ importers: specifier: ^3.0.6 version: 3.0.6 '@types/node': - specifier: 20.13.0 + specifier: ^20 version: 20.13.0 '@types/react': - specifier: ^18.3.12 - version: 18.3.18 + specifier: ^19 + version: 19.0.8 '@types/react-dom': - specifier: ^18.3.1 - version: 18.3.5(@types/react@18.3.18) + specifier: ^19 + version: 19.0.3(@types/react@19.0.8) '@types/react-lazy-load-image-component': specifier: ^1.6.4 version: 1.6.4 + autocannon: + specifier: ^8.0.0 + version: 8.0.0 autoprefixer: specifier: ^10.0.1 - version: 10.4.20(postcss@8.5.0) - class-variance-authority: - specifier: ^0.7.0 - version: 0.7.1 + version: 10.4.20(postcss@8.5.1) clsx: specifier: ^2.1.1 version: 2.1.1 - cors: - specifier: ^2.8.5 - version: 2.8.5 - express: - specifier: ^4.21.1 - version: 4.21.2 jest: specifier: ^29.7.0 version: 29.7.0(@types/node@20.13.0)(babel-plugin-macros@3.1.0) jest-environment-jsdom: specifier: ^29.7.0 version: 29.7.0 + lighthouse: + specifier: ^12.3.0 + version: 12.3.0 msw: specifier: ^2.5.2 - version: 2.7.0(@types/node@20.13.0)(typescript@5.4.5) + version: 2.7.0(@types/node@20.13.0)(typescript@5.7.3) postcss: specifier: ^8 - version: 8.5.0 + version: 8.5.1 + puppeteer: + specifier: ^24.1.1 + version: 24.1.1(typescript@5.7.3) tailwind-merge: specifier: ^2.5.4 version: 2.6.0 tailwindcss: specifier: ^3.3.0 version: 3.4.17 - tailwindcss-animate: - specifier: ^1.0.7 - version: 1.0.7(tailwindcss@3.4.17) ts-jest: specifier: ^29.1.1 - version: 29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@20.13.0)(babel-plugin-macros@3.1.0))(typescript@5.4.5) + version: 29.2.5(@babel/core@7.26.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.7))(jest@29.7.0(@types/node@20.13.0)(babel-plugin-macros@3.1.0))(typescript@5.7.3) typescript: - specifier: 5.4.5 - version: 5.4.5 + specifier: ^5 + version: 5.7.3 packages: '@adobe/css-tools@4.4.1': @@ -419,10 +452,16 @@ packages: } engines: { node: '>=6.0.0' } - '@asamuzakjp/css-color@2.8.2': + '@asamuzakjp/css-color@2.8.3': + resolution: + { + integrity: sha512-GIc76d9UI1hCvOATjZPyHFmE5qhRccp3/zGfMPapK3jBi+yocEzp6BBB0UnfRYP9NP4FANqUZYb0hnfs3TM3hw==, + } + + '@assemblyscript/loader@0.19.23': resolution: { - integrity: sha512-RtWv9jFN2/bLExuZgFFZ0I3pWWeezAHGgrmjqGGWclATl1aDe3yhCUaI0Ilkp6OCk9zX7+FjvDasEX8Q9Rxc5w==, + integrity: sha512-ulkCYfFbYj01ie1MDOyxv2F6SpRN1TOj7fQxbP07D6HmeR+gr2JLSmINKjga2emB+b1L2KGrFKBTc+e00p54nw==, } '@babel/code-frame@7.26.2': @@ -439,10 +478,10 @@ packages: } engines: { node: '>=6.9.0' } - '@babel/core@7.26.0': + '@babel/core@7.26.7': resolution: { - integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==, + integrity: sha512-SRijHmF0PSPgLIBYlWnG0hyeJLwXE2CgpsXaMOrtt2yp9/86ALw6oUlj9KYuZ0JN07T4eBMVIW4li/9S1j2BGA==, } engines: { node: '>=6.9.0' } @@ -618,17 +657,17 @@ packages: } engines: { node: '>=6.9.0' } - '@babel/helpers@7.26.0': + '@babel/helpers@7.26.7': resolution: { - integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==, + integrity: sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==, } engines: { node: '>=6.9.0' } - '@babel/parser@7.26.5': + '@babel/parser@7.26.7': resolution: { - integrity: sha512-SRJ4jYmXRqV1/Xc+TIVG84WjHBXKlxO9sHQnA2Pf12QQEAp1LOh6kDzNHXcUnbH1QI0FDoPPVOt+vyUDucxpaw==, + integrity: sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==, } engines: { node: '>=6.0.0' } hasBin: true @@ -1297,19 +1336,19 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-typeof-symbol@7.25.9': + '@babel/plugin-transform-typeof-symbol@7.26.7': resolution: { - integrity: sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA==, + integrity: sha512-jfoTXXZTgGg36BmhqT3cAYK5qkmqvJpvNrPhaK/52Vgjhw4Rq29s9UqpWWV0D6yuRmgiFH/BUVlkl96zJWqnaw==, } engines: { node: '>=6.9.0' } peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-typescript@7.26.5': + '@babel/plugin-transform-typescript@7.26.7': resolution: { - integrity: sha512-GJhPO0y8SD5EYVCy2Zr+9dSZcEgaSmq5BLR0Oc25TOEhC+ba49vUAGZFjy8v79z9E1mdldq4x9d1xgh4L1d5dQ==, + integrity: sha512-5cJurntg+AT+cgelGP9Bt788DKiAw9gIMSMU2NJrLAilnj0m8WZWUNZPSLOmadYsujHutpgElO+50foX+ib/Wg==, } engines: { node: '>=6.9.0' } peerDependencies: @@ -1351,10 +1390,10 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/preset-env@7.26.0': + '@babel/preset-env@7.26.7': resolution: { - integrity: sha512-H84Fxq0CQJNdPFT2DrfnylZ3cf5K43rGfWK4LJGPpjKHiZlk0/RzwEus3PDDZZg+/Er7lCA03MVacueUuXdzfw==, + integrity: sha512-Ycg2tnXwixaXOVb29rana8HNPgLVBof8qqtNQ9LE22IoyZboQbGSxI6ZySMdW3K5nAe6gu35IaJefUJflhUFTQ==, } engines: { node: '>=6.9.0' } peerDependencies: @@ -1386,10 +1425,10 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/runtime@7.26.0': + '@babel/runtime@7.26.7': resolution: { - integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==, + integrity: sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ==, } engines: { node: '>=6.9.0' } @@ -1407,10 +1446,10 @@ packages: } engines: { node: '>=6.9.0' } - '@babel/traverse@7.26.5': + '@babel/traverse@7.26.7': resolution: { - integrity: sha512-rkOSPOw+AXbgtwUga3U4u8RpoK9FEFWBNAlTpcnkLFjL5CT+oyHNuUUC/xx6XefEJ16r38r8Bc/lfp6rYuHeJQ==, + integrity: sha512-1x1sgeyRLC3r5fQOM0/xtQKsYjyxmFjaOrLJNtZ81inNjyJHGIolTULPiSc/2qe1/qfpFLisLQYFnnZl7QoedA==, } engines: { node: '>=6.9.0' } @@ -1421,10 +1460,10 @@ packages: } engines: { node: '>=6.9.0' } - '@babel/types@7.26.5': + '@babel/types@7.26.7': resolution: { - integrity: sha512-L6mZmwFDK6Cjh1nRCLXpa6no13ZIioJDz7mdkzHv399pThrTa/k0nUlNaenOeh2kWu/iaOQYElEpKPUswUa9Vg==, + integrity: sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==, } engines: { node: '>=6.9.0' } @@ -1452,6 +1491,13 @@ packages: integrity: sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==, } + '@colors/colors@1.5.0': + resolution: + { + integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==, + } + engines: { node: '>=0.1.90' } + '@csstools/color-helpers@5.0.1': resolution: { @@ -1495,6 +1541,13 @@ packages: } engines: { node: '>=18' } + '@discoveryjs/json-ext@0.5.7': + resolution: + { + integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==, + } + engines: { node: '>=10.0.0' } + '@emnapi/core@1.3.1': resolution: { @@ -2266,10 +2319,10 @@ packages: } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } - '@faker-js/faker@9.3.0': + '@faker-js/faker@9.4.0': resolution: { - integrity: sha512-r0tJ3ZOkMd9xsu3VRfqlFR6cz0V/jFYRswAIpC+m/DIfAUXq7g8N7wTAlhSANySXYGKzGryfDXwtwsY8TxEIDw==, + integrity: sha512-85+k0AxaZSTowL0gXp8zYWDIrWclTbRPg/pm/V0dSFZ6W6D4lhcG3uuZl4zLsEKfEvs69xDbLN2cHQudwp95JA==, } engines: { node: '>=18.0.0', npm: '>=9.0.0' } @@ -2300,6 +2353,36 @@ packages: integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==, } + '@formatjs/ecma402-abstract@2.3.2': + resolution: + { + integrity: sha512-6sE5nyvDloULiyOMbOTJEEgWL32w+VHkZQs8S02Lnn8Y/O5aQhjOEXwWzvR7SsBE/exxlSpY2EsWZgqHbtLatg==, + } + + '@formatjs/fast-memoize@2.2.6': + resolution: + { + integrity: sha512-luIXeE2LJbQnnzotY1f2U2m7xuQNj2DA8Vq4ce1BY9ebRZaoPB1+8eZ6nXpLzsxuW5spQxr7LdCg+CApZwkqkw==, + } + + '@formatjs/icu-messageformat-parser@2.11.0': + resolution: + { + integrity: sha512-Hp81uTjjdTk3FLh/dggU5NK7EIsVWc5/ZDWrIldmf2rBuPejuZ13CZ/wpVE2SToyi4EiroPTQ1XJcJuZFIxTtw==, + } + + '@formatjs/icu-skeleton-parser@1.8.12': + resolution: + { + integrity: sha512-QRAY2jC1BomFQHYDMcZtClqHR55EEnB96V7Xbk/UiBodsuFc5kujybzt87+qj1KqmJozFhk6n4KiT1HKwAkcfg==, + } + + '@formatjs/intl-localematcher@0.5.10': + resolution: + { + integrity: sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q==, + } + '@hookform/resolvers@3.10.0': resolution: { @@ -2674,12 +2757,6 @@ packages: } engines: { node: '>=6.0.0' } - '@jridgewell/source-map@0.3.6': - resolution: - { - integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==, - } - '@jridgewell/sourcemap-codec@1.5.0': resolution: { @@ -2704,6 +2781,12 @@ packages: integrity: sha512-MT8IXl1rhTe8VcwnkhgFtWra6sRYNsl/I7nE9aw6QxwvPReKmRDmyBmEIeXwnKSGHRe19OJhu4/A9ciKPyVdMA==, } + '@lexical/clipboard@0.27.1': + resolution: + { + integrity: sha512-QmoX77lwDT2VxNljFkpMtQmGGof6Bv9TEv0i9c5403zE/avVm3g9Tk47bClo+Y0NruESwb79VODbdF29pbVKPQ==, + } + '@lexical/code@0.21.0': resolution: { @@ -2782,6 +2865,12 @@ packages: integrity: sha512-kNkDUaDe/Awypaw8JZn65BzT1gwNj2bNkaGFcmIkXUrTtiqlvgYvKvJeOKLkoAb/i2xq990ZAbHOsJrJm1jMbw==, } + '@lexical/html@0.27.1': + resolution: + { + integrity: sha512-WmMPfRurd5BccSmpe+DGZA8N4CDLThek/2/rjaba+zotk9ZTbXktDO5Ie4982V4mZ2VKQfMFflKou6eY8NLcvA==, + } + '@lexical/link@0.21.0': resolution: { @@ -2794,6 +2883,12 @@ packages: integrity: sha512-HRaOp7prtcbHjbgq8AjJ4O02jYb8pTeS8RrGcgIRhCOq3/EcsSb1dXMwuraqmh9oxbuFyEu/JE31EFksiOW6qA==, } + '@lexical/link@0.27.1': + resolution: + { + integrity: sha512-VJ9SOBZSxcuLg5Hgtj3zhxykggzV/iS4aKx4Ggj2ZFGIgvyBVCHrs+jCCqS4GLxR5cEc7m5fkYfNQLXMojjpyg==, + } + '@lexical/list@0.21.0': resolution: { @@ -2806,6 +2901,12 @@ packages: integrity: sha512-TI3WyWk3avv9uaJwaq8V+m9zxLRgnzXDYNS0rREafnW09rDpaFkpVmDuX+PZVR3NqPlwVt+slWVSBuyfguAFbA==, } + '@lexical/list@0.27.1': + resolution: + { + integrity: sha512-sCKwRGyRbTyZjF63ENAJPPoVKV6612vcNxRjYmGYQr9R6m834kl9lsa+H5eCDBeYxtQB7eHbDHbYj90Sd1jYHQ==, + } + '@lexical/mark@0.21.0': resolution: { @@ -2908,6 +3009,12 @@ packages: integrity: sha512-xoehAURMZJZYf046GHUXiv8FSv5zTobhwDD2dML4fmNHPp9NxugkWHlNUinTK/b+jGgjSYVsqpEKPBmue4ZHdQ==, } + '@lexical/selection@0.27.1': + resolution: + { + integrity: sha512-cSaZXE9SXvHMGMa0gbkrlFNXXMDJVu2D0ojwUKaw4HQC79FVXmFj4m1k8htuneu35hU+fiaE8D4fBIZ4/TlESg==, + } + '@lexical/table@0.21.0': resolution: { @@ -2920,6 +3027,12 @@ packages: integrity: sha512-Qs+iuwSVkV4OGTt+JdL9hvyl/QO3X9waH70L5Fxu9JmQk/jLl02tIGXbE38ocJkByfpyk4PrphoXt6l7CugJZA==, } + '@lexical/table@0.27.1': + resolution: + { + integrity: sha512-+g8MheTEVBCO2E/4ocjJEa4X6A30zYvHO2MPGXJQH30Rm2xeznbC/R84tkqA+uL59SrsJKeFgxrlislXzQZGtg==, + } + '@lexical/text@0.21.0': resolution: { @@ -2944,6 +3057,12 @@ packages: integrity: sha512-yXEkF6fj32+mJblCoP0ZT/vA0S05FA0nRUkVrvGX6sbZ9y+cIzuIbBoHi4z1ytutcWHQrwCK4TsN9hPYBIlb2w==, } + '@lexical/utils@0.27.1': + resolution: + { + integrity: sha512-llihlrcAFZWB7o22Wnl7/UsXtfMQHwKRnsYPSJuwj1egVG3c3fCRyv5sbD7Xys3rdvuNUHLpSQa812huN11/JQ==, + } + '@lexical/yjs@0.21.0': resolution: { @@ -2960,14 +3079,6 @@ packages: peerDependencies: yjs: '>=13.5.22' - '@lottiefiles/react-lottie-player@3.5.4': - resolution: - { - integrity: sha512-2FptWtHQ+o7MzdsMKSvNZ1Mz7xtKSYI0WL9HjZ1r+CvsXR3lbLQUDp7Pwx6qhg0Akm4VluQ+8/D1S1fcr1Ao4w==, - } - peerDependencies: - react: 16 - 18 - '@lottiefiles/react-lottie-player@3.6.0': resolution: { @@ -2976,20 +3087,17 @@ packages: peerDependencies: react: 16 - 19 - '@mdn/browser-compat-data@5.6.29': + '@mdn/browser-compat-data@5.6.33': resolution: { - integrity: sha512-+s2wY7ftjoXf3UwyvR7U4EKDCpUuxlCdnv2OP5BAk1uvoCgUVVU0GtVNolD5Gj+1oVWX1y5a4Yj/LIaThUDmGA==, + integrity: sha512-56GEfTIgrPXAc9EL3MbJmv0QiIxZR/UzpOCzPtCGsW/IYr+3Bj1DbtBXnNMD2vOMqv1UVdCxy7LXxRcU+wxO5A==, } - '@mswjs/http-middleware@0.10.3': + '@minimistjs/subarg@1.0.0': resolution: { - integrity: sha512-6CoX9IivDF7hggORdA4vX6uz+pkY1urGQMhmviHmYya/0b4EXwmhaXlGLQG3G29heqb3qdjp61V0+E2xRtyR5A==, + integrity: sha512-Q/ONBiM2zNeYUy0mVSO44mWWKYM3UHuEK43PKIOzJCbvUnPoMH1K+gk3cf1kgnCVJFlWmddahQQCmrmBGlk9jQ==, } - engines: { node: '>=18' } - peerDependencies: - msw: '>=2.0.0' '@mswjs/interceptors@0.37.5': resolution: @@ -3004,79 +3112,85 @@ packages: integrity: sha512-9zESzOO5aDByvhIAsOy9TbpZ0Ur2AJbUI7UT73kcUTS2mxAMHOBaa1st/jAymNoCtvrit99kkzT1FZuXVcgfIQ==, } - '@next/env@15.1.4': + '@next/bundle-analyzer@15.1.6': + resolution: + { + integrity: sha512-hGzQyDqJzFHcHNCyTqM3o05BpVq5tGnRODccZBVJDBf5Miv/26UJPMB0wh9L9j3ylgHC+0/v8BaBnBBek1rC6Q==, + } + + '@next/env@15.1.6': resolution: { - integrity: sha512-2fZ5YZjedi5AGaeoaC0B20zGntEHRhi2SdWcu61i48BllODcAmmtj8n7YarSPt4DaTsJaBFdxQAVEVzgmx2Zpw==, + integrity: sha512-d9AFQVPEYNr+aqokIiPLNK/MTyt3DWa/dpKveiAaVccUadFbhFEvY6FXYX2LJO2Hv7PHnLBu2oWwB4uBuHjr/w==, } - '@next/swc-darwin-arm64@15.1.4': + '@next/swc-darwin-arm64@15.1.6': resolution: { - integrity: sha512-wBEMBs+np+R5ozN1F8Y8d/Dycns2COhRnkxRc+rvnbXke5uZBHkUGFgWxfTXn5rx7OLijuUhyfB+gC/ap58dDw==, + integrity: sha512-u7lg4Mpl9qWpKgy6NzEkz/w0/keEHtOybmIl0ykgItBxEM5mYotS5PmqTpo+Rhg8FiOiWgwr8USxmKQkqLBCrw==, } engines: { node: '>= 10' } cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@15.1.4': + '@next/swc-darwin-x64@15.1.6': resolution: { - integrity: sha512-7sgf5rM7Z81V9w48F02Zz6DgEJulavC0jadab4ZsJ+K2sxMNK0/BtF8J8J3CxnsJN3DGcIdC260wEKssKTukUw==, + integrity: sha512-x1jGpbHbZoZ69nRuogGL2MYPLqohlhnT9OCU6E6QFewwup+z+M6r8oU47BTeJcWsF2sdBahp5cKiAcDbwwK/lg==, } engines: { node: '>= 10' } cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@15.1.4': + '@next/swc-linux-arm64-gnu@15.1.6': resolution: { - integrity: sha512-JaZlIMNaJenfd55kjaLWMfok+vWBlcRxqnRoZrhFQrhM1uAehP3R0+Aoe+bZOogqlZvAz53nY/k3ZyuKDtT2zQ==, + integrity: sha512-jar9sFw0XewXsBzPf9runGzoivajeWJUc/JkfbLTC4it9EhU8v7tCRLH7l5Y1ReTMN6zKJO0kKAGqDk8YSO2bg==, } engines: { node: '>= 10' } cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@15.1.4': + '@next/swc-linux-arm64-musl@15.1.6': resolution: { - integrity: sha512-7EBBjNoyTO2ipMDgCiORpwwOf5tIueFntKjcN3NK+GAQD7OzFJe84p7a2eQUeWdpzZvhVXuAtIen8QcH71ZCOQ==, + integrity: sha512-+n3u//bfsrIaZch4cgOJ3tXCTbSxz0s6brJtU3SzLOvkJlPQMJ+eHVRi6qM2kKKKLuMY+tcau8XD9CJ1OjeSQQ==, } engines: { node: '>= 10' } cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@15.1.4': + '@next/swc-linux-x64-gnu@15.1.6': resolution: { - integrity: sha512-9TGEgOycqZFuADyFqwmK/9g6S0FYZ3tphR4ebcmCwhL8Y12FW8pIBKJvSwV+UBjMkokstGNH+9F8F031JZKpHw==, + integrity: sha512-SpuDEXixM3PycniL4iVCLyUyvcl6Lt0mtv3am08sucskpG0tYkW1KlRhTgj4LI5ehyxriVVcfdoxuuP8csi3kQ==, } engines: { node: '>= 10' } cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@15.1.4': + '@next/swc-linux-x64-musl@15.1.6': resolution: { - integrity: sha512-0578bLRVDJOh+LdIoKvgNDz77+Bd85c5JrFgnlbI1SM3WmEQvsjxTA8ATu9Z9FCiIS/AliVAW2DV/BDwpXbtiQ==, + integrity: sha512-L4druWmdFSZIIRhF+G60API5sFB7suTbDRhYWSjiw0RbE+15igQvE2g2+S973pMGvwN3guw7cJUjA/TmbPWTHQ==, } engines: { node: '>= 10' } cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@15.1.4': + '@next/swc-win32-arm64-msvc@15.1.6': resolution: { - integrity: sha512-JgFCiV4libQavwII+kncMCl30st0JVxpPOtzWcAI2jtum4HjYaclobKhj+JsRu5tFqMtA5CJIa0MvYyuu9xjjQ==, + integrity: sha512-s8w6EeqNmi6gdvM19tqKKWbCyOBvXFbndkGHl+c9YrzsLARRdCHsD9S1fMj8gsXm9v8vhC8s3N8rjuC/XrtkEg==, } engines: { node: '>= 10' } cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@15.1.4': + '@next/swc-win32-x64-msvc@15.1.6': resolution: { - integrity: sha512-xxsJy9wzq7FR5SqPCUqdgSXiNXrMuidgckBa8nH9HtjjxsilgcN6VgXF6tZ3uEWuVEadotQJI8/9EQ6guTC4Yw==, + integrity: sha512-6xomMuu54FAFxttYr5PJbEfu96godcxBTRk1OhAvJq0/EnmFU/Ybiax30Snis4vdWZ9LGpf7Roy5fSs7v/5ROQ==, } engines: { node: '>= 10' } cpu: [x64] @@ -3103,91 +3217,91 @@ packages: } engines: { node: '>= 8' } - '@nx/nx-darwin-arm64@20.3.1': + '@nx/nx-darwin-arm64@20.3.3': resolution: { - integrity: sha512-bx++T9/8l4PK1yDTxPnROT7RG8CkWGkxKC0D7xlS/YQzE7CelDfgNYu0Bd7upZF4gafW2Uz3dd3j6WhvZLxbbg==, + integrity: sha512-4C7ShMrqp1vbH1ZgvSlkt0f35hJcqKtRcf8n/tCck46rnMkj4egXi3K1dE6uQcOorwiD1ttAr0DHcI1TTqcNXw==, } engines: { node: '>= 10' } cpu: [arm64] os: [darwin] - '@nx/nx-darwin-x64@20.3.1': + '@nx/nx-darwin-x64@20.3.3': resolution: { - integrity: sha512-elg2GiSivMHU1iLFYZ+FojM2V/FmTlC8e5FKM6nZ+bIqeoBoJm8Rxxe/kEtcsPdvjj+YiKSmXOP9s45DJb9WWw==, + integrity: sha512-OUtJ7gA09pJC+a+RcZf1bGbMM4T7a/IcPb97z1xOoxr5Wm2s8BGBQUW2CKJ5gCp5iI1pGo44F12u0G9gbYClow==, } engines: { node: '>= 10' } cpu: [x64] os: [darwin] - '@nx/nx-freebsd-x64@20.3.1': + '@nx/nx-freebsd-x64@20.3.3': resolution: { - integrity: sha512-1iKZOCcU7bVAC2kdoukfJ7AOTLBhm69+vPff3HCJQ0DI/5ZbmiaPeBMsAVFtJ0jFGix8yYIhgvtXgDEfbXXRFQ==, + integrity: sha512-q4SABgKYWPGOcdfRZne6n8HF4CzltRL5nJ3q093jQAUO93yPXtWzhQBaKZIZr6aPoqq0/NuH6xY4gNo4w9F8Bg==, } engines: { node: '>= 10' } cpu: [x64] os: [freebsd] - '@nx/nx-linux-arm-gnueabihf@20.3.1': + '@nx/nx-linux-arm-gnueabihf@20.3.3': resolution: { - integrity: sha512-LAteJ1/mWYdvj7zpXuWRUq1lvUiV6YVXCdFK3+7lDW+qvW3bb5zzUwbVDAF/pPeTjBrsdHDzSWOCLm/LKtYtMw==, + integrity: sha512-e07PJcVsBT/Aelo/Vj6hLplDZamGCZ3zOJpW3XVBhdG4DC4sn+jodsdrIASoEpmF70VB89lzQsm9GrAgQPaWOA==, } engines: { node: '>= 10' } cpu: [arm] os: [linux] - '@nx/nx-linux-arm64-gnu@20.3.1': + '@nx/nx-linux-arm64-gnu@20.3.3': resolution: { - integrity: sha512-2Qf+6NcAeODELyJR+V9hjC9kl2DwJTdI7Bw+BuiyXftfPHvZ86P//FC8kPjNaJCEEm/ZStP6Jcb1zlp4Eo2wBw==, + integrity: sha512-1Z9chlN0/hWzliMer7TvdLT8cb6BKpGjZ15a+rQuUbO/CyLhY21Ct+lXtnaBERnNPYJpNOJlrbBDuF/9wpZ4CQ==, } engines: { node: '>= 10' } cpu: [arm64] os: [linux] - '@nx/nx-linux-arm64-musl@20.3.1': + '@nx/nx-linux-arm64-musl@20.3.3': resolution: { - integrity: sha512-8S8jlN6GFQpRakZ2ZVWq6eFnLVrEObIaxnYD0QMbsMf+qiedDJt+cDh1xebcPRvgpSgJVlJ8P6hun5+K/FiQDQ==, + integrity: sha512-RrLgujPU5NfDrsDRa7Y2isxGb8XkoQeJkTMUl1xmBK2Qnf4jAUn0PH0ULWrRMNgChi4nYUTn/Sf+2m6Uyoqcfw==, } engines: { node: '>= 10' } cpu: [arm64] os: [linux] - '@nx/nx-linux-x64-gnu@20.3.1': + '@nx/nx-linux-x64-gnu@20.3.3': resolution: { - integrity: sha512-qC2On2qwYCtn/Kt8epvUn0H3NY6zG9yYhiNjkm6RvVTDmvogFQ4gtfiWSRP/EnabCRqM8FACDIO/ws5CnRBX+Q==, + integrity: sha512-/WmCnPxv1eR8tyYiFp4XoMbcXrJ8a/OIw1rpZZ5ceMKgH8lPaF2/KFf04JZZygrCKletEdqqIojBXz4AHoaueQ==, } engines: { node: '>= 10' } cpu: [x64] os: [linux] - '@nx/nx-linux-x64-musl@20.3.1': + '@nx/nx-linux-x64-musl@20.3.3': resolution: { - integrity: sha512-KKwHSfV1PEKW82eJ8vxZTPepoaLbaXH/aI0VOKZbBO4ytGyGUr9wFuWPsyo06rK7qtSD7w9bN7xpiBGQk0QTsg==, + integrity: sha512-y4BJsR0fgJrXY3P7GkWfUZAeQEHMTXvaRHvzJfBSBPmnVcVZDYNTfEQYnslp8m8ahKdlJwtflxzykJ4Bwf55fw==, } engines: { node: '>= 10' } cpu: [x64] os: [linux] - '@nx/nx-win32-arm64-msvc@20.3.1': + '@nx/nx-win32-arm64-msvc@20.3.3': resolution: { - integrity: sha512-YujkXXHn9rhtwZRDxiaxSPOMX7JkfGmXAFdyEfxhE3Dc/HjFgI+xJZ478/atttR7DWIwGpQJVLpbFWbFFpoNNg==, + integrity: sha512-BHqZitBaGT9ybv386B5QKxP5N66+xpTiYlKClzQ44o6Ca8QxnkugI64exBdcQyj+DRiL6HJhN14kaPJ1KrsKRA==, } engines: { node: '>= 10' } cpu: [arm64] os: [win32] - '@nx/nx-win32-x64-msvc@20.3.1': + '@nx/nx-win32-x64-msvc@20.3.3': resolution: { - integrity: sha512-Os8iCamvHhE5noQKFE9D9xkiI529918tufTYmEhJ9ZmLU/ybVA0We6r7gXjYzdNfA3DtwfGXvNvUpy3u+pZXOg==, + integrity: sha512-6HcbAKghEypt4aMAoDjPn2sa6FG0MyiDabpV/cVLKokK09ngyy6qQDa5vSCUSDwI542XBxqtcv0AcZi7Ez+XUQ==, } engines: { node: '>= 10' } cpu: [x64] @@ -3211,6 +3325,12 @@ packages: integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==, } + '@paulirish/trace_engine@0.0.39': + resolution: + { + integrity: sha512-2Y/ejHX5DDi5bjfWY/0c/BLVSfQ61Jw1Hy60Hnh0hfEO632D3FVctkzT4Q/lVAdvIPR0bUaok9JDTr1pu/OziA==, + } + '@pkgjs/parseargs@0.11.0': resolution: { @@ -3225,10 +3345,10 @@ packages: } engines: { node: ^12.20.0 || ^14.18.0 || >=16.0.0 } - '@playwright/test@1.49.1': + '@playwright/test@1.50.0': resolution: { - integrity: sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==, + integrity: sha512-ZGNXbt+d65EGjBORQHuYKj+XhCewlwpnSd/EDuLPZGSiEWmgOJB5RmMCCYGy5aMfTs9wx61RivfDKi8H/hcMvw==, } engines: { node: '>=18' } hasBin: true @@ -3239,6 +3359,22 @@ packages: integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==, } + '@puppeteer/browsers@2.6.1': + resolution: + { + integrity: sha512-aBSREisdsGH890S2rQqK82qmQYU3uFpSH8wcZWHgHzl3LfzsxAKbLNiAG9mO8v1Y0UICBeClICxPJvyr0rcuxg==, + } + engines: { node: '>=18' } + hasBin: true + + '@puppeteer/browsers@2.7.0': + resolution: + { + integrity: sha512-bO61XnTuopsz9kvtfqhVbH6LTM1koxK0IlBR+yuVrM2LB7mk8+5o1w18l5zqd5cs8xlf+ntgambqRqGifMDjog==, + } + engines: { node: '>=18' } + hasBin: true + '@radix-ui/primitive@1.1.1': resolution: { @@ -3301,10 +3437,10 @@ packages: '@types/react': optional: true - '@radix-ui/react-dialog@1.1.4': + '@radix-ui/react-dialog@1.1.5': resolution: { - integrity: sha512-Ur7EV1IwQGCyaAuyDRiOLA5JIUZxELJljF+MbM/2NC0BYwfuRrbpS30BiQBJrVruscgUkieKkqXYDOoByaxIoA==, + integrity: sha512-LaO3e5h/NOEL4OfXjxD43k9Dx+vn+8n+PCFt6uhX/BADFflllyv3WJG6rgvvSVBxpTch938Qq/LGc2MMxipXPw==, } peerDependencies: '@types/react': '*' @@ -3329,10 +3465,10 @@ packages: '@types/react': optional: true - '@radix-ui/react-dismissable-layer@1.1.3': + '@radix-ui/react-dismissable-layer@1.1.4': resolution: { - integrity: sha512-onrWn/72lQoEucDmJnr8uczSNTujT0vJnA/X5+3AkChVPowr8n1yvIKIabhWyMQeMvvmdpsvcyDqx3X1LEXCPg==, + integrity: sha512-XDUI0IVYVSwjMXxM6P4Dfti7AH+Y4oS/TB+sglZ/EXc7cqLwGAmp1NlMrcUjj7ks6R5WTZuWKv44FBbLpwU3sA==, } peerDependencies: '@types/react': '*' @@ -3345,10 +3481,10 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-dropdown-menu@2.1.4': + '@radix-ui/react-dropdown-menu@2.1.5': resolution: { - integrity: sha512-iXU1Ab5ecM+yEepGAWK8ZhMyKX4ubFdCNtol4sT9D0OVErG9PNElfx3TQhjw7n7BC5nFVz68/5//clWy+8TXzA==, + integrity: sha512-50ZmEFL1kOuLalPKHrLWvPFMons2fGx9TqQCWlPwDVpbAnaUJ1g4XNcKqFNMQymYU0kKWR4MDDi+9vUQBGFgcQ==, } peerDependencies: '@types/react': '*' @@ -3389,6 +3525,14 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-icons@1.3.2': + resolution: + { + integrity: sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==, + } + peerDependencies: + react: ^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc + '@radix-ui/react-id@1.1.0': resolution: { @@ -3401,10 +3545,10 @@ packages: '@types/react': optional: true - '@radix-ui/react-menu@2.1.4': + '@radix-ui/react-menu@2.1.5': resolution: { - integrity: sha512-BnOgVoL6YYdHAG6DtXONaR29Eq4nvbi8rutrV/xlr3RQCMMb3yqP85Qiw/3NReozrSW+4dfLkK+rc1hb4wPU/A==, + integrity: sha512-uH+3w5heoMJtqVCgYOtYVMECk1TOrkUn0OG0p5MqXC0W2ppcuVeESbou8PTHoqAjbdTEK19AGXBWcEtR5WpEQg==, } peerDependencies: '@types/react': '*' @@ -3599,158 +3743,200 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.30.1': + '@rollup/rollup-android-arm-eabi@4.32.0': resolution: { - integrity: sha512-pSWY+EVt3rJ9fQ3IqlrEUtXh3cGqGtPDH1FQlNZehO2yYxCHEX1SPsz1M//NXwYfbTlcKr9WObLnJX9FsS9K1Q==, + integrity: sha512-G2fUQQANtBPsNwiVFg4zKiPQyjVKZCUdQUol53R8E71J7AsheRMV/Yv/nB8giOcOVqP7//eB5xPqieBYZe9bGg==, } cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.30.1': + '@rollup/rollup-android-arm64@4.32.0': resolution: { - integrity: sha512-/NA2qXxE3D/BRjOJM8wQblmArQq1YoBVJjrjoTSBS09jgUisq7bqxNHJ8kjCHeV21W/9WDGwJEWSN0KQ2mtD/w==, + integrity: sha512-qhFwQ+ljoymC+j5lXRv8DlaJYY/+8vyvYmVx074zrLsu5ZGWYsJNLjPPVJJjhZQpyAKUGPydOq9hRLLNvh1s3A==, } cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.30.1': + '@rollup/rollup-darwin-arm64@4.32.0': resolution: { - integrity: sha512-r7FQIXD7gB0WJ5mokTUgUWPl0eYIH0wnxqeSAhuIwvnnpjdVB8cRRClyKLQr7lgzjctkbp5KmswWszlwYln03Q==, + integrity: sha512-44n/X3lAlWsEY6vF8CzgCx+LQaoqWGN7TzUfbJDiTIOjJm4+L2Yq+r5a8ytQRGyPqgJDs3Rgyo8eVL7n9iW6AQ==, } cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.30.1': + '@rollup/rollup-darwin-x64@4.32.0': resolution: { - integrity: sha512-x78BavIwSH6sqfP2xeI1hd1GpHL8J4W2BXcVM/5KYKoAD3nNsfitQhvWSw+TFtQTLZ9OmlF+FEInEHyubut2OA==, + integrity: sha512-F9ct0+ZX5Np6+ZDztxiGCIvlCaW87HBdHcozUfsHnj1WCUTBUubAoanhHUfnUHZABlElyRikI0mgcw/qdEm2VQ==, } cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.30.1': + '@rollup/rollup-freebsd-arm64@4.32.0': resolution: { - integrity: sha512-HYTlUAjbO1z8ywxsDFWADfTRfTIIy/oUlfIDmlHYmjUP2QRDTzBuWXc9O4CXM+bo9qfiCclmHk1x4ogBjOUpUQ==, + integrity: sha512-JpsGxLBB2EFXBsTLHfkZDsXSpSmKD3VxXCgBQtlPcuAqB8TlqtLcbeMhxXQkCDv1avgwNjF8uEIbq5p+Cee0PA==, } cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.30.1': + '@rollup/rollup-freebsd-x64@4.32.0': resolution: { - integrity: sha512-1MEdGqogQLccphhX5myCJqeGNYTNcmTyaic9S7CG3JhwuIByJ7J05vGbZxsizQthP1xpVx7kd3o31eOogfEirw==, + integrity: sha512-wegiyBT6rawdpvnD9lmbOpx5Sph+yVZKHbhnSP9MqUEDX08G4UzMU+D87jrazGE7lRSyTRs6NEYHtzfkJ3FjjQ==, } cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.30.1': + '@rollup/rollup-linux-arm-gnueabihf@4.32.0': resolution: { - integrity: sha512-PaMRNBSqCx7K3Wc9QZkFx5+CX27WFpAMxJNiYGAXfmMIKC7jstlr32UhTgK6T07OtqR+wYlWm9IxzennjnvdJg==, + integrity: sha512-3pA7xecItbgOs1A5H58dDvOUEboG5UfpTq3WzAdF54acBbUM+olDJAPkgj1GRJ4ZqE12DZ9/hNS2QZk166v92A==, } cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.30.1': + '@rollup/rollup-linux-arm-musleabihf@4.32.0': resolution: { - integrity: sha512-B8Rcyj9AV7ZlEFqvB5BubG5iO6ANDsRKlhIxySXcF1axXYUyqwBok+XZPgIYGBgs7LDXfWfifxhw0Ik57T0Yug==, + integrity: sha512-Y7XUZEVISGyge51QbYyYAEHwpGgmRrAxQXO3siyYo2kmaj72USSG8LtlQQgAtlGfxYiOwu+2BdbPjzEpcOpRmQ==, } cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.30.1': + '@rollup/rollup-linux-arm64-gnu@4.32.0': resolution: { - integrity: sha512-hqVyueGxAj3cBKrAI4aFHLV+h0Lv5VgWZs9CUGqr1z0fZtlADVV1YPOij6AhcK5An33EXaxnDLmJdQikcn5NEw==, + integrity: sha512-r7/OTF5MqeBrZo5omPXcTnjvv1GsrdH8a8RerARvDFiDwFpDVDnJyByYM/nX+mvks8XXsgPUxkwe/ltaX2VH7w==, } cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.30.1': + '@rollup/rollup-linux-arm64-musl@4.32.0': resolution: { - integrity: sha512-i4Ab2vnvS1AE1PyOIGp2kXni69gU2DAUVt6FSXeIqUCPIR3ZlheMW3oP2JkukDfu3PsexYRbOiJrY+yVNSk9oA==, + integrity: sha512-HJbifC9vex9NqnlodV2BHVFNuzKL5OnsV2dvTw6e1dpZKkNjPG6WUq+nhEYV6Hv2Bv++BXkwcyoGlXnPrjAKXw==, } cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.30.1': + '@rollup/rollup-linux-loongarch64-gnu@4.32.0': resolution: { - integrity: sha512-fARcF5g296snX0oLGkVxPmysetwUk2zmHcca+e9ObOovBR++9ZPOhqFUM61UUZ2EYpXVPN1redgqVoBB34nTpQ==, + integrity: sha512-VAEzZTD63YglFlWwRj3taofmkV1V3xhebDXffon7msNz4b14xKsz7utO6F8F4cqt8K/ktTl9rm88yryvDpsfOw==, } cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.30.1': + '@rollup/rollup-linux-powerpc64le-gnu@4.32.0': resolution: { - integrity: sha512-GLrZraoO3wVT4uFXh67ElpwQY0DIygxdv0BNW9Hkm3X34wu+BkqrDrkcsIapAY+N2ATEbvak0XQ9gxZtCIA5Rw==, + integrity: sha512-Sts5DST1jXAc9YH/iik1C9QRsLcCoOScf3dfbY5i4kH9RJpKxiTBXqm7qU5O6zTXBTEZry69bGszr3SMgYmMcQ==, } cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.30.1': + '@rollup/rollup-linux-riscv64-gnu@4.32.0': resolution: { - integrity: sha512-0WKLaAUUHKBtll0wvOmh6yh3S0wSU9+yas923JIChfxOaaBarmb/lBKPF0w/+jTVozFnOXJeRGZ8NvOxvk/jcw==, + integrity: sha512-qhlXeV9AqxIyY9/R1h1hBD6eMvQCO34ZmdYvry/K+/MBs6d1nRFLm6BOiITLVI+nFAAB9kUB6sdJRKyVHXnqZw==, } cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.30.1': + '@rollup/rollup-linux-s390x-gnu@4.32.0': resolution: { - integrity: sha512-GWFs97Ruxo5Bt+cvVTQkOJ6TIx0xJDD/bMAOXWJg8TCSTEK8RnFeOeiFTxKniTc4vMIaWvCplMAFBt9miGxgkA==, + integrity: sha512-8ZGN7ExnV0qjXa155Rsfi6H8M4iBBwNLBM9lcVS+4NcSzOFaNqmt7djlox8pN1lWrRPMRRQ8NeDlozIGx3Omsw==, } cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.30.1': + '@rollup/rollup-linux-x64-gnu@4.32.0': resolution: { - integrity: sha512-UtgGb7QGgXDIO+tqqJ5oZRGHsDLO8SlpE4MhqpY9Llpzi5rJMvrK6ZGhsRCST2abZdBqIBeXW6WPD5fGK5SDwg==, + integrity: sha512-VDzNHtLLI5s7xd/VubyS10mq6TxvZBp+4NRWoW+Hi3tgV05RtVm4qK99+dClwTN1McA6PHwob6DEJ6PlXbY83A==, } cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.30.1': + '@rollup/rollup-linux-x64-musl@4.32.0': resolution: { - integrity: sha512-V9U8Ey2UqmQsBT+xTOeMzPzwDzyXmnAoO4edZhL7INkwQcaW1Ckv3WJX3qrrp/VHaDkEWIBWhRwP47r8cdrOow==, + integrity: sha512-qcb9qYDlkxz9DxJo7SDhWxTWV1gFuwznjbTiov289pASxlfGbaOD54mgbs9+z94VwrXtKTu+2RqwlSTbiOqxGg==, } cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.30.1': + '@rollup/rollup-win32-arm64-msvc@4.32.0': resolution: { - integrity: sha512-WabtHWiPaFF47W3PkHnjbmWawnX/aE57K47ZDT1BXTS5GgrBUEpvOzq0FI0V/UYzQJgdb8XlhVNH8/fwV8xDjw==, + integrity: sha512-pFDdotFDMXW2AXVbfdUEfidPAk/OtwE/Hd4eYMTNVVaCQ6Yl8et0meDaKNL63L44Haxv4UExpv9ydSf3aSayDg==, } cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.30.1': + '@rollup/rollup-win32-ia32-msvc@4.32.0': resolution: { - integrity: sha512-pxHAU+Zv39hLUTdQQHUVHf4P+0C47y/ZloorHpzs2SXMRqeAWmGghzAhfOlzFHHwjvgokdFAhC4V+6kC1lRRfw==, + integrity: sha512-/TG7WfrCAjeRNDvI4+0AAMoHxea/USWhAzf9PVDFHbcqrQ7hMMKp4jZIy4VEjk72AAfN5k4TiSMRXRKf/0akSw==, } cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.30.1': + '@rollup/rollup-win32-x64-msvc@4.32.0': resolution: { - integrity: sha512-D6qjsXGcvhTjv0kI4fU8tUuBDF/Ueee4SVX79VfNDXZa64TfCW1Slkb6Z7O1p7vflqZjcmOVdZlqf8gvJxc6og==, + integrity: sha512-5hqO5S3PTEO2E5VjCePxv40gIgyS2KvO7E7/vvC/NbIW4SIRamkMr1hqj+5Y67fbBWv/bQLB6KelBQmXlyCjWA==, } cpu: [x64] os: [win32] + '@sentry-internal/tracing@7.120.3': + resolution: + { + integrity: sha512-Ausx+Jw1pAMbIBHStoQ6ZqDZR60PsCByvHdw/jdH9AqPrNE9xlBSf9EwcycvmrzwyKspSLaB52grlje2cRIUMg==, + } + engines: { node: '>=8' } + + '@sentry/core@7.120.3': + resolution: + { + integrity: sha512-vyy11fCGpkGK3qI5DSXOjgIboBZTriw0YDx/0KyX5CjIjDDNgp5AGgpgFkfZyiYiaU2Ww3iFuKo4wHmBusz1uA==, + } + engines: { node: '>=8' } + + '@sentry/integrations@7.120.3': + resolution: + { + integrity: sha512-6i/lYp0BubHPDTg91/uxHvNui427df9r17SsIEXa2eKDwQ9gW2qRx5IWgvnxs2GV/GfSbwcx4swUB3RfEWrXrQ==, + } + engines: { node: '>=8' } + + '@sentry/node@7.120.3': + resolution: + { + integrity: sha512-t+QtekZedEfiZjbkRAk1QWJPnJlFBH/ti96tQhEq7wmlk3VszDXraZvLWZA0P2vXyglKzbWRGkT31aD3/kX+5Q==, + } + engines: { node: '>=8' } + + '@sentry/types@7.120.3': + resolution: + { + integrity: sha512-C4z+3kGWNFJ303FC+FxAd4KkHvxpNFYAFN8iMIgBwJdpIl25KZ8Q/VdGn0MLLUEHNLvjob0+wvwlcRBBNLXOow==, + } + engines: { node: '>=8' } + + '@sentry/utils@7.120.3': + resolution: + { + integrity: sha512-UDAOQJtJDxZHQ5Nm1olycBIsz2wdGX8SdzyGVHmD8EOQYAeDZQyIlQYohDe9nazdIOQLZCIc3fU0G9gqVLkaGQ==, + } + engines: { node: '>=8' } + '@sinclair/typebox@0.27.8': resolution: { @@ -3769,22 +3955,22 @@ packages: integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==, } - '@stackflow/config@1.2.0': + '@stackflow/config@1.2.1': resolution: { - integrity: sha512-rb7LJf0BolnldZoIcds+38jDzXoMI+4tigq6R2OmEeZRdJCDDbfAZZnQpdhUNTSAMatZIL7b0U/X4JE2R9gCQA==, + integrity: sha512-ri2grP+BOOsIZvsFFLHZKhd1HJVPg4j8Ums09waP+voQm3hzFPnMKCEMh/cMzcSxKb+j9skWTCzDEJMTeXTQBA==, } - '@stackflow/core@1.1.0': + '@stackflow/core@1.1.1': resolution: { - integrity: sha512-IjrcTM9itdmolgURRciq8xVpI0C3mmTRRoBn2smcRSjeDBk/qUM/k37QlL+uGgcoV+kaKXpkeVXhhSIFsYAKjw==, + integrity: sha512-jU5zMRwfazPkojn5o35sR8zmtUMyQM+SBRUtIVMiyksZrvPKAtQA4h9sZ5a1/OLyXnoRKsheYwR6cRqm6pt8DQ==, } - '@stackflow/link@1.6.0': + '@stackflow/link@1.6.1': resolution: { - integrity: sha512-GQJPJJTL2la3+w5baKUAzfzieNSRm7nWuKwq2vjBHygnN668UVpUNwepc96lvaCj75x4u1l6c7w5IOfjCCwPTQ==, + integrity: sha512-M8Vu/rNn1MzFhBMJDzfCQuFU8zKTRVY1W4PoqIXVDIn9mZS1Kr8+Iaak3yODM/lPts8EZkIuRKrbf+8O3jxXaQ==, } peerDependencies: '@stackflow/core': ^1.1.0-canary.0 @@ -3805,10 +3991,10 @@ packages: '@types/react': '>=16.8.0' react: '>=16.8.0' - '@stackflow/plugin-history-sync@1.7.0': + '@stackflow/plugin-history-sync@1.7.1': resolution: { - integrity: sha512-7iJXWizB7x8cONzPdeVBkSPdRkR3Q8jVZv/84y5hyh6+MdZ9fbOKK7rtP1ZjN2/hk+kDBmXdE6jLN2b3dDWIQQ==, + integrity: sha512-qgmqil2We1Hjcg6pu5jvrOUAi98hbdQF2/d3KGnOurCUc8PYxl8o0JKbyTkoOMBRDeBOVgtQBwpHEYB3t3jwvQ==, } peerDependencies: '@stackflow/config': ^1.0.1-canary.0 @@ -3851,10 +4037,10 @@ packages: '@types/react': '>=16.8.0' react: '>=16.8.0' - '@stackflow/react@1.4.1': + '@stackflow/react@1.4.2': resolution: { - integrity: sha512-udbUmfYz2hn36+rPej3NgQqLyR0hlj+Wllh+rWS5TBz7A1lkdvWkKzOx1lhrTrNhShGn06auNlys2OWne/N8lQ==, + integrity: sha512-PuWD9t/1Ah7bhiea/B1mrQ0TDwQnqNCRJ20lArvmkObmJ+hi963LQvq8T1Sl5Q+STFZWZ4HOTx04FSZDkbUgNg==, } peerDependencies: '@stackflow/config': ^1.0.1-canary.0 @@ -3982,100 +4168,100 @@ packages: } engines: { node: '>=14' } - '@swc/core-darwin-arm64@1.10.7': + '@swc/core-darwin-arm64@1.10.11': resolution: { - integrity: sha512-SI0OFg987P6hcyT0Dbng3YRISPS9uhLX1dzW4qRrfqQdb0i75lPJ2YWe9CN47HBazrIA5COuTzrD2Dc0TcVsSQ==, + integrity: sha512-ZpgEaNcx2e5D+Pd0yZGVbpSrEDOEubn7r2JXoNBf0O85lPjUm3HDzGRfLlV/MwxRPAkwm93eLP4l7gYnc50l3g==, } engines: { node: '>=10' } cpu: [arm64] os: [darwin] - '@swc/core-darwin-x64@1.10.7': + '@swc/core-darwin-x64@1.10.11': resolution: { - integrity: sha512-RFIAmWVicD/l3RzxgHW0R/G1ya/6nyMspE2cAeDcTbjHi0I5qgdhBWd6ieXOaqwEwiCd0Mot1g2VZrLGoBLsjQ==, + integrity: sha512-szObinnq2o7spXMDU5pdunmUeLrfV67Q77rV+DyojAiGJI1RSbEQotLOk+ONOLpoapwGUxOijFG4IuX1xiwQ2g==, } engines: { node: '>=10' } cpu: [x64] os: [darwin] - '@swc/core-linux-arm-gnueabihf@1.10.7': + '@swc/core-linux-arm-gnueabihf@1.10.11': resolution: { - integrity: sha512-QP8vz7yELWfop5mM5foN6KkLylVO7ZUgWSF2cA0owwIaziactB2hCPZY5QU690coJouk9KmdFsPWDnaCFUP8tg==, + integrity: sha512-tVE8aXQwd8JUB9fOGLawFJa76nrpvp3dvErjozMmWSKWqtoeO7HV83aOrVtc8G66cj4Vq7FjTE9pOJeV1FbKRw==, } engines: { node: '>=10' } cpu: [arm] os: [linux] - '@swc/core-linux-arm64-gnu@1.10.7': + '@swc/core-linux-arm64-gnu@1.10.11': resolution: { - integrity: sha512-NgUDBGQcOeLNR+EOpmUvSDIP/F7i/OVOKxst4wOvT5FTxhnkWrW+StJGKj+DcUVSK5eWOYboSXr1y+Hlywwokw==, + integrity: sha512-geFkENU5GMEKO7FqHOaw9HVlpQEW10nICoM6ubFc0hXBv8dwRXU4vQbh9s/isLSFRftw1m4jEEWixAnXSw8bxQ==, } engines: { node: '>=10' } cpu: [arm64] os: [linux] - '@swc/core-linux-arm64-musl@1.10.7': + '@swc/core-linux-arm64-musl@1.10.11': resolution: { - integrity: sha512-gp5Un3EbeSThBIh6oac5ZArV/CsSmTKj5jNuuUAuEsML3VF9vqPO+25VuxCvsRf/z3py+xOWRaN2HY/rjMeZog==, + integrity: sha512-2mMscXe/ivq8c4tO3eQSbQDFBvagMJGlalXCspn0DgDImLYTEnt/8KHMUMGVfh0gMJTZ9q4FlGLo7mlnbx99MQ==, } engines: { node: '>=10' } cpu: [arm64] os: [linux] - '@swc/core-linux-x64-gnu@1.10.7': + '@swc/core-linux-x64-gnu@1.10.11': resolution: { - integrity: sha512-k/OxLLMl/edYqbZyUNg6/bqEHTXJT15l9WGqsl/2QaIGwWGvles8YjruQYQ9d4h/thSXLT9gd8bExU2D0N+bUA==, + integrity: sha512-eu2apgDbC4xwsigpl6LS+iyw6a3mL6kB4I+6PZMbFF2nIb1Dh7RGnu70Ai6mMn1o80fTmRSKsCT3CKMfVdeNFg==, } engines: { node: '>=10' } cpu: [x64] os: [linux] - '@swc/core-linux-x64-musl@1.10.7': + '@swc/core-linux-x64-musl@1.10.11': resolution: { - integrity: sha512-XeDoURdWt/ybYmXLCEE8aSiTOzEn0o3Dx5l9hgt0IZEmTts7HgHHVeRgzGXbR4yDo0MfRuX5nE1dYpTmCz0uyA==, + integrity: sha512-0n+wPWpDigwqRay4IL2JIvAqSKCXv6nKxPig9M7+epAlEQlqX+8Oq/Ap3yHtuhjNPb7HmnqNJLCXT1Wx+BZo0w==, } engines: { node: '>=10' } cpu: [x64] os: [linux] - '@swc/core-win32-arm64-msvc@1.10.7': + '@swc/core-win32-arm64-msvc@1.10.11': resolution: { - integrity: sha512-nYAbi/uLS+CU0wFtBx8TquJw2uIMKBnl04LBmiVoFrsIhqSl+0MklaA9FVMGA35NcxSJfcm92Prl2W2LfSnTqQ==, + integrity: sha512-7+bMSIoqcbXKosIVd314YjckDRPneA4OpG1cb3/GrkQTEDXmWT3pFBBlJf82hzJfw7b6lfv6rDVEFBX7/PJoLA==, } engines: { node: '>=10' } cpu: [arm64] os: [win32] - '@swc/core-win32-ia32-msvc@1.10.7': + '@swc/core-win32-ia32-msvc@1.10.11': resolution: { - integrity: sha512-+aGAbsDsIxeLxw0IzyQLtvtAcI1ctlXVvVcXZMNXIXtTURM876yNrufRo4ngoXB3jnb1MLjIIjgXfFs/eZTUSw==, + integrity: sha512-6hkLl4+3KjP/OFTryWxpW7YFN+w4R689TSPwiII4fFgsFNupyEmLWWakKfkGgV2JVA59L4Oi02elHy/O1sbgtw==, } engines: { node: '>=10' } cpu: [ia32] os: [win32] - '@swc/core-win32-x64-msvc@1.10.7': + '@swc/core-win32-x64-msvc@1.10.11': resolution: { - integrity: sha512-TBf4clpDBjF/UUnkKrT0/th76/zwvudk5wwobiTFqDywMApHip5O0VpBgZ+4raY2TM8k5+ujoy7bfHb22zu17Q==, + integrity: sha512-kKNE2BGu/La2k2WFHovenqZvGQAHRIU+rd2/6a7D6EiQ6EyimtbhUqjCCZ+N1f5fIAnvM+sMdLiQJq4jdd/oOQ==, } engines: { node: '>=10' } cpu: [x64] os: [win32] - '@swc/core@1.10.7': + '@swc/core@1.10.11': resolution: { - integrity: sha512-py91kjI1jV5D5W/Q+PurBdGsdU5TFbrzamP7zSCqLdMcHkKi3rQEM5jkQcZr0MXXSJTaayLxS3MWYTBIkzPDrg==, + integrity: sha512-3zGU5y3S20cAwot9ZcsxVFNsSVaptG+dKdmAxORSE3EX7ixe1Xn5kUwLlgIsM4qrwTUWCJDLNhRS+2HLFivcDg==, } engines: { node: '>=10' } peerDependencies: @@ -4102,31 +4288,31 @@ packages: integrity: sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==, } - '@tanstack/query-core@5.64.1': + '@tanstack/query-core@5.64.2': resolution: { - integrity: sha512-978Wx4Wl4UJZbmvU/rkaM9cQtXXrbhK0lsz/UZhYIbyKYA8E4LdomTwyh2GHZ4oU0BKKoDH4YlKk2VscCUgNmg==, + integrity: sha512-hdO8SZpWXoADNTWXV9We8CwTkXU88OVWRBcsiFrk7xJQnhm6WRlweDzMD+uH+GnuieTBVSML6xFa17C2cNV8+g==, } - '@tanstack/query-devtools@5.62.16': + '@tanstack/query-devtools@5.64.2': resolution: { - integrity: sha512-3ff6UBJr0H3nIhfLSl9911rvKqXf0u4B58jl0uYdDWLqPk9pCvYIbxC35cGxK2+8INl4IaFVUHb/IdgWrNkg3Q==, + integrity: sha512-3DautR5UpVZdk/qNIhioZVF7g8fdQZ1U98sBEEk4Tzz3tihSBNMPgwlP40nzgbPEDBIrn/j/oyyvNBVSo083Vw==, } - '@tanstack/react-query-devtools@5.64.1': + '@tanstack/react-query-devtools@5.64.2': resolution: { - integrity: sha512-8ajcGE3wXYlb4KuJnkFYkJwJKc/qmPNTpQD7YTgLRMBPTGGp1xk7VMzxL87DoXuweO8luplUUblJJ3noVs/luQ==, + integrity: sha512-+ZjJVnPzc8BUV/Eklu2k9T/IAyAyvwoCHqOaOrk2sbU33LFhM52BpX4eyENXn0bx5LwV3DJZgEQlIzucoemfGQ==, } peerDependencies: - '@tanstack/react-query': ^5.64.1 + '@tanstack/react-query': ^5.64.2 react: ^18 || ^19 - '@tanstack/react-query@5.64.1': + '@tanstack/react-query@5.64.2': resolution: { - integrity: sha512-vW5ggHpIO2Yjj44b4sB+Fd3cdnlMJppXRBJkEHvld6FXh3j5dwWJoQo7mGtKI2RbSFyiyu/PhGAy0+Vv5ev9Eg==, + integrity: sha512-3pakNscZNm8KJkxmovvtZ4RaXLyiYYobwleTMvpIGUoKRa8j8VlrQKNl5W8VUEfVfZKkikvXVddLuWMbcSCA1Q==, } peerDependencies: react: ^18 || ^19 @@ -4164,10 +4350,10 @@ packages: react-test-renderer: optional: true - '@testing-library/react@16.1.0': + '@testing-library/react@16.2.0': resolution: { - integrity: sha512-Q2ToPvg0KsVL0ohND9A3zLJWcOXXcO8IDu3fj11KhNt0UlCWyFyvnCIBkd12tidB2lkiVRG8VFqdhcqhqnAQtg==, + integrity: sha512-2cSskAvA1QNtKc8Y9VJQRv0tm3hLVgxRGDB+KYhIaPQJ1I+RHbhIXcM+zClKXzMes/wshsMVzf4B9vS4IZpqDQ==, } engines: { node: '>=18' } peerDependencies: @@ -4182,10 +4368,10 @@ packages: '@types/react-dom': optional: true - '@testing-library/user-event@14.5.2': + '@testing-library/user-event@14.6.1': resolution: { - integrity: sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==, + integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==, } engines: { node: '>=12', npm: '>=6' } peerDependencies: @@ -4198,6 +4384,12 @@ packages: } engines: { node: '>= 10' } + '@tootallnate/quickjs-emscripten@0.23.0': + resolution: + { + integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==, + } + '@trivago/prettier-plugin-sort-imports@4.3.0': resolution: { @@ -4253,72 +4445,30 @@ packages: integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==, } - '@types/body-parser@1.19.5': - resolution: - { - integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==, - } - - '@types/connect@3.4.38': - resolution: - { - integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==, - } - '@types/cookie@0.6.0': resolution: { integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==, } - '@types/cors@2.8.17': - resolution: - { - integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==, - } - '@types/crypto-js@4.2.2': resolution: { integrity: sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==, } - '@types/eslint@9.6.1': - resolution: - { - integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==, - } - '@types/estree@1.0.6': resolution: { integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==, } - '@types/express-serve-static-core@5.0.4': - resolution: - { - integrity: sha512-5kz9ScmzBdzTgB/3susoCgfqNDzBjvLL4taparufgSvlwjdLy6UyUy9T/tCpYd2GIdIilCatC4iSQS0QSYHt0w==, - } - - '@types/express@5.0.0': - resolution: - { - integrity: sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==, - } - '@types/graceful-fs@4.1.9': resolution: { integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==, } - '@types/http-errors@2.0.4': - resolution: - { - integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==, - } - '@types/istanbul-lib-coverage@2.0.6': resolution: { @@ -4355,18 +4505,6 @@ packages: integrity: sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==, } - '@types/json-schema@7.0.15': - resolution: - { - integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==, - } - - '@types/mime@1.3.5': - resolution: - { - integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==, - } - '@types/node@20.13.0': resolution: { @@ -4379,31 +4517,13 @@ packages: integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==, } - '@types/prop-types@15.7.14': - resolution: - { - integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==, - } - - '@types/qs@6.9.18': - resolution: - { - integrity: sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==, - } - - '@types/range-parser@1.2.7': - resolution: - { - integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==, - } - - '@types/react-dom@18.3.5': + '@types/react-dom@19.0.3': resolution: { - integrity: sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==, + integrity: sha512-0Knk+HJiMP/qOZgMyNFamlIjw9OFCsyC2ZbigmEEyXXixgre6IQpm/4V+r3qH4GC1JPvRJKInw+on2rV6YZLeA==, } peerDependencies: - '@types/react': ^18.0.0 + '@types/react': ^19.0.0 '@types/react-lazy-load-image-component@1.6.4': resolution: @@ -4411,22 +4531,10 @@ packages: integrity: sha512-8pFPeDPF4yVG4lU1/ixZidJEEDZmEOYOTYDvmIu2IAabyuv97Q7n/93nMCocHvQ7vD1czKGiW+op55D9m3MkdA==, } - '@types/react@18.3.18': - resolution: - { - integrity: sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==, - } - - '@types/send@0.17.4': - resolution: - { - integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==, - } - - '@types/serve-static@1.15.7': + '@types/react@19.0.8': resolution: { - integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==, + integrity: sha512-9P/o1IGdfmQxrujGbIMDyYaaCykhLKc0NGCtYcECNUr9UAaDe4gwvV9bR6tvd5Br1SG0j+PBpbKr2UYY8CwqSw==, } '@types/stack-utils@2.0.3': @@ -4459,6 +4567,12 @@ packages: integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==, } + '@types/yauzl@2.10.3': + resolution: + { + integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==, + } + '@typescript-eslint/eslint-plugin@7.18.0': resolution: { @@ -4541,10 +4655,10 @@ packages: } engines: { node: ^18.18.0 || >=20.0.0 } - '@ungap/structured-clone@1.2.1': + '@ungap/structured-clone@1.3.0': resolution: { - integrity: sha512-fEzPV3hSkSMltkw152tJKNARhOupqbH96MZWyRjNaYZOMIzbrTeQDG+MTc6Mr2pgzFQzFxAfmhGDNP5QK++2ZA==, + integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==, } '@vanilla-extract/css@1.17.0': @@ -4678,13 +4792,6 @@ packages: } deprecated: Use your platform's native atob() and btoa() methods instead - accepts@1.3.8: - resolution: - { - integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==, - } - engines: { node: '>= 0.6' } - acorn-globals@7.0.1: resolution: { @@ -4841,12 +4948,6 @@ packages: } engines: { node: '>= 0.4' } - array-flatten@1.1.1: - resolution: - { - integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==, - } - array-includes@3.1.8: resolution: { @@ -4909,6 +5010,20 @@ packages: integrity: sha512-ht3Dm6Zr7SXv6t1Ra6gFo0+kLDglHGrEbYihTkcycrbHw7WCcuhBzPlJYHEsIpycaUwzsJHje+vUcxXUX4ztTA==, } + ast-types@0.13.4: + resolution: + { + integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==, + } + engines: { node: '>=4' } + + async-function@1.0.0: + resolution: + { + integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==, + } + engines: { node: '>= 0.4' } + async@3.2.6: resolution: { @@ -4921,6 +5036,13 @@ packages: integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==, } + autocannon@8.0.0: + resolution: + { + integrity: sha512-fMMcWc2JPFcUaqHeR6+PbmEpTxCrPZyBUM95oG4w3ngJ8NfBNas/ZXA+pTHXLqJ0UlFVTcy05GC25WxKx/M20A==, + } + hasBin: true + autoprefixer@10.4.20: resolution: { @@ -4938,12 +5060,25 @@ packages: } engines: { node: '>= 0.4' } + axe-core@4.10.2: + resolution: + { + integrity: sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==, + } + engines: { node: '>=4' } + axios@1.7.9: resolution: { integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==, } + b4a@1.6.7: + resolution: + { + integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==, + } + babel-jest@29.7.0: resolution: { @@ -5021,40 +5156,80 @@ packages: integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==, } - base64-js@1.5.1: + bare-events@2.5.4: resolution: { - integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==, + integrity: sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==, } - binary-extensions@2.3.0: + bare-fs@4.0.1: resolution: { - integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==, + integrity: sha512-ilQs4fm/l9eMfWY2dY0WCIUplSUp7U0CT1vrqMg1MUdeZl4fypu5UP0XcDBK5WBQPJAKP1b7XEodISmekH/CEg==, } - engines: { node: '>=8' } + engines: { bare: '>=1.7.0' } - bl@4.1.0: + bare-os@3.4.0: resolution: { - integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==, + integrity: sha512-9Ous7UlnKbe3fMi7Y+qh0DwAup6A1JkYgPnjvMDNOlmnxNRQvQ/7Nst+OnUQKzk0iAT0m9BisbDVp9gCv8+ETA==, } + engines: { bare: '>=1.6.0' } - body-parser@1.20.3: + bare-path@3.0.0: resolution: { - integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==, + integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==, } - engines: { node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16 } - boolbase@1.0.0: + bare-stream@2.6.4: resolution: { - integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==, + integrity: sha512-G6i3A74FjNq4nVrrSTUz5h3vgXzBJnjmWAVlBWaZETkgu+LgKd7AiyOml3EDJY1AHlIbBHKDXE+TUT53Ff8OaA==, } - - brace-expansion@1.1.11: - resolution: + peerDependencies: + bare-buffer: '*' + bare-events: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + bare-events: + optional: true + + base64-js@1.5.1: + resolution: + { + integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==, + } + + basic-ftp@5.0.5: + resolution: + { + integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==, + } + engines: { node: '>=10.0.0' } + + binary-extensions@2.3.0: + resolution: + { + integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==, + } + engines: { node: '>=8' } + + bl@4.1.0: + resolution: + { + integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==, + } + + boolbase@1.0.0: + resolution: + { + integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==, + } + + brace-expansion@1.1.11: + resolution: { integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==, } @@ -5093,6 +5268,12 @@ packages: integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==, } + buffer-crc32@0.2.13: + resolution: + { + integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==, + } + buffer-from@1.1.2: resolution: { @@ -5112,13 +5293,6 @@ packages: } engines: { node: '>=10.16.0' } - bytes@3.1.2: - resolution: - { - integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==, - } - engines: { node: '>= 0.8' } - cac@6.7.14: resolution: { @@ -5175,10 +5349,10 @@ packages: } engines: { node: '>=10' } - caniuse-lite@1.0.30001692: + caniuse-lite@1.0.30001695: resolution: { - integrity: sha512-A95VKan0kdtrsnMubMKxEKUKImOPSuCpYgxSQBo036P5YYgVIcOYJEgt/txJWqObiRQeISNCfef9nvlQ0vbV7A==, + integrity: sha512-vHyLade6wTgI2u1ec3WQBxv+2BrTERV28UXQu9LO6lZ9pYeMk34vjXFLOxo1A4UBA8XTL4njRQZdno/yYaSmWw==, } chai@5.1.2: @@ -5209,6 +5383,12 @@ packages: } engines: { node: '>=10' } + char-spinner@1.0.1: + resolution: + { + integrity: sha512-acv43vqJ0+N0rD+Uw3pDHSxP30FHrywu2NO6/wBaHChJIizpDeBUd6NjqhNhy9LGaEAhZAXn46QzmlAvIWd16g==, + } + check-error@2.1.1: resolution: { @@ -5223,6 +5403,30 @@ packages: } engines: { node: '>= 8.10.0' } + chrome-launcher@1.1.2: + resolution: + { + integrity: sha512-YclTJey34KUm5jB1aEJCq807bSievi7Nb/TU4Gu504fUYi3jw3KCIaH6L7nFWQhdEgH3V+wCh+kKD1P5cXnfxw==, + } + engines: { node: '>=12.13.0' } + hasBin: true + + chromium-bidi@0.11.0: + resolution: + { + integrity: sha512-6CJWHkNRoyZyjV9Rwv2lYONZf1Xm0IuDyNq97nwSsxxP3wf5Bwy15K5rOvVKMtJ127jJBmxFUanSAOjgFRxgrA==, + } + peerDependencies: + devtools-protocol: '*' + + chromium-bidi@1.1.0: + resolution: + { + integrity: sha512-HislCEczCuamWm3+55Lig9XKmMF13K+BGKum9rwtDAzgUAHT4h5jNwhDmD4U20VoVUG8ujnv9UZ89qiIf5uF8w==, + } + peerDependencies: + devtools-protocol: '*' + ci-info@3.9.0: resolution: { @@ -5236,12 +5440,6 @@ packages: integrity: sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==, } - class-variance-authority@0.7.1: - resolution: - { - integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==, - } - cli-cursor@3.1.0: resolution: { @@ -5256,6 +5454,13 @@ packages: } engines: { node: '>=6' } + cli-table3@0.6.5: + resolution: + { + integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==, + } + engines: { node: 10.* || >= 12.* } + cli-width@4.1.0: resolution: { @@ -5322,6 +5527,13 @@ packages: integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==, } + color-support@1.1.3: + resolution: + { + integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==, + } + hasBin: true + color@4.2.3: resolution: { @@ -5336,12 +5548,6 @@ packages: } engines: { node: '>= 0.8' } - commander@2.20.3: - resolution: - { - integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==, - } - commander@4.1.1: resolution: { @@ -5362,19 +5568,12 @@ packages: integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==, } - content-disposition@0.5.4: + configstore@5.0.1: resolution: { - integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==, + integrity: sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==, } - engines: { node: '>= 0.6' } - - content-type@1.0.5: - resolution: - { - integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==, - } - engines: { node: '>= 0.6' } + engines: { node: '>=8' } convert-source-map@1.9.0: resolution: @@ -5388,19 +5587,6 @@ packages: integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==, } - cookie-signature@1.0.6: - resolution: - { - integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==, - } - - cookie@0.7.1: - resolution: - { - integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==, - } - engines: { node: '>= 0.6' } - cookie@0.7.2: resolution: { @@ -5408,26 +5594,12 @@ packages: } engines: { node: '>= 0.6' } - cookie@1.0.2: - resolution: - { - integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==, - } - engines: { node: '>=18' } - core-js-compat@3.40.0: resolution: { integrity: sha512-0XEDpr5y5mijvw8Lbc6E5AkjrHfp7eEoPlu36SWeAbcL8fn1G1ANe8DBlo2XoNN89oVpxWwOjYIPVzR4ZvsKCQ==, } - cors@2.8.5: - resolution: - { - integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==, - } - engines: { node: '>= 0.10' } - cosmiconfig@7.1.0: resolution: { @@ -5447,6 +5619,18 @@ packages: typescript: optional: true + cosmiconfig@9.0.0: + resolution: + { + integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==, + } + engines: { node: '>=14' } + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + create-jest@29.7.0: resolution: { @@ -5455,6 +5639,12 @@ packages: engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } hasBin: true + cross-argv@2.0.0: + resolution: + { + integrity: sha512-YIaY9TR5Nxeb8SMdtrU8asWVM4jqJDNDYlKV21LxtYcfNJhp1kEsgSa6qXwXgzN0WQWGODps0+TlGp2xQSHwOg==, + } + cross-spawn@7.0.6: resolution: { @@ -5468,6 +5658,19 @@ packages: integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==, } + crypto-random-string@2.0.0: + resolution: + { + integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==, + } + engines: { node: '>=8' } + + csp_evaluator@1.1.1: + resolution: + { + integrity: sha512-N3ASg0C4kNPUaNxt1XAvzHIVuzdtr8KLgfk1O8WDyimp1GisPAHESupArO2ieHk9QWbrJ/WkQODyh21Ps/xhxw==, + } + css-select@5.1.0: resolution: { @@ -5548,6 +5751,13 @@ packages: integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==, } + data-uri-to-buffer@6.0.2: + resolution: + { + integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==, + } + engines: { node: '>= 14' } + data-urls@3.0.2: resolution: { @@ -5589,6 +5799,12 @@ packages: integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==, } + debounce@1.2.1: + resolution: + { + integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==, + } + debug@2.6.9: resolution: { @@ -5612,18 +5828,11 @@ packages: supports-color: optional: true - decimal.js@10.4.3: - resolution: - { - integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==, - } - - decode-uri-component@0.4.1: + decimal.js@10.5.0: resolution: { - integrity: sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==, + integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==, } - engines: { node: '>=14.16' } dedent@1.5.3: resolution: @@ -5689,19 +5898,19 @@ packages: } engines: { node: '>= 0.4' } - delayed-stream@1.0.0: + degenerator@5.0.1: resolution: { - integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==, + integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==, } - engines: { node: '>=0.4.0' } + engines: { node: '>= 14' } - depd@2.0.0: + delayed-stream@1.0.0: resolution: { - integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==, + integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==, } - engines: { node: '>= 0.8' } + engines: { node: '>=0.4.0' } dequal@2.0.3: resolution: @@ -5710,13 +5919,6 @@ packages: } engines: { node: '>=6' } - destroy@1.2.0: - resolution: - { - integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==, - } - engines: { node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16 } - detect-libc@2.0.3: resolution: { @@ -5737,6 +5939,24 @@ packages: integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==, } + devtools-protocol@0.0.1312386: + resolution: + { + integrity: sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==, + } + + devtools-protocol@0.0.1367902: + resolution: + { + integrity: sha512-XxtPuC3PGakY6PD7dG66/o8KwJ/LkH2/EKe19Dcw58w53dv4/vSQEkn/SzuyhHE2q4zPgCkxQBxus3VV4ql+Pg==, + } + + devtools-protocol@0.0.1380148: + resolution: + { + integrity: sha512-1CJABgqLxbYxVI+uJY/UDUHJtJ0KZTSjNYJYKqd9FRoXT33WDakDHNxRapMEgzeJ/C3rcs01+avshMnPmKQbvA==, + } + didyoumean@1.2.2: resolution: { @@ -5828,6 +6048,13 @@ packages: integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==, } + dot-prop@5.3.0: + resolution: + { + integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==, + } + engines: { node: '>=8' } + dotenv-expand@11.0.7: resolution: { @@ -5849,16 +6076,16 @@ packages: } engines: { node: '>= 0.4' } - eastasianwidth@0.2.0: + duplexer@0.1.2: resolution: { - integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==, + integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==, } - ee-first@1.1.1: + eastasianwidth@0.2.0: resolution: { - integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==, + integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==, } ejs@3.1.10: @@ -5869,10 +6096,10 @@ packages: engines: { node: '>=0.10.0' } hasBin: true - electron-to-chromium@1.5.80: + electron-to-chromium@1.5.88: resolution: { - integrity: sha512-LTrKpW0AqIuHwmlVNV+cjFYTnXtM9K37OGhpe0ZI10ScPSxqVSryZHIY3WnCS5NSYbBODRTZyhRMS2h5FAEqAw==, + integrity: sha512-K3C2qf1o+bGzbilTDCTBhTQcMS9KW60yTAaTeeXsfvQuTDDwlokLam/AdqlqcSy9u4UainDgsHV23ksXAOgamw==, } embla-carousel-react@8.5.2: @@ -5916,20 +6143,6 @@ packages: integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==, } - encodeurl@1.0.2: - resolution: - { - integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==, - } - engines: { node: '>= 0.8' } - - encodeurl@2.0.0: - resolution: - { - integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==, - } - engines: { node: '>= 0.8' } - end-of-stream@1.4.4: resolution: { @@ -5950,6 +6163,13 @@ packages: } engines: { node: '>=0.12' } + env-paths@2.2.1: + resolution: + { + integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==, + } + engines: { node: '>=6' } + error-ex@1.3.2: resolution: { @@ -5990,10 +6210,10 @@ packages: integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==, } - es-object-atoms@1.0.1: + es-object-atoms@1.1.1: resolution: { - integrity: sha512-BPOBuyUF9QIVhuNLhbToCLHP6+0MHwZ7xLBkPPCZqK4JmpJgGnv10035STzzQwFpqdzNFMB3irvDI63IagvDwA==, + integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==, } engines: { node: '>= 0.4' } @@ -6048,12 +6268,6 @@ packages: } engines: { node: '>=6' } - escape-html@1.0.3: - resolution: - { - integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==, - } - escape-string-regexp@1.0.5: resolution: { @@ -6101,10 +6315,10 @@ packages: peerDependencies: eslint: ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 - eslint-plugin-prettier@5.2.1: + eslint-plugin-prettier@5.2.3: resolution: { - integrity: sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==, + integrity: sha512-qJ+y0FfCp/mQYQ/vWQ3s7eUlFEL4PyKfAJxsnYTJ4YT73nsJBWqmEpFryxV9OeUiqmsTsYJ5Y+KDNaeP31wrRw==, } engines: { node: ^14.18.0 || >=16.0.0 } peerDependencies: @@ -6217,13 +6431,6 @@ packages: } engines: { node: '>=0.10.0' } - etag@1.8.1: - resolution: - { - integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==, - } - engines: { node: '>= 0.6' } - execa@5.1.1: resolution: { @@ -6252,12 +6459,13 @@ packages: } engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } - express@4.21.2: + extract-zip@2.0.1: resolution: { - integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==, + integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==, } - engines: { node: '>= 0.10.0' } + engines: { node: '>= 10.17.0' } + hasBin: true fast-deep-equal@3.1.3: resolution: @@ -6271,6 +6479,12 @@ packages: integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==, } + fast-fifo@1.3.2: + resolution: + { + integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==, + } + fast-glob@3.3.3: resolution: { @@ -6302,10 +6516,16 @@ packages: integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==, } - fdir@6.4.2: + fd-slicer@1.1.0: + resolution: + { + integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==, + } + + fdir@6.4.3: resolution: { - integrity: sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==, + integrity: sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==, } peerDependencies: picomatch: ^3 || ^4 @@ -6346,20 +6566,6 @@ packages: } engines: { node: '>=8' } - filter-obj@5.1.0: - resolution: - { - integrity: sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==, - } - engines: { node: '>=14.16' } - - finalhandler@1.3.1: - resolution: - { - integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==, - } - engines: { node: '>= 0.8' } - find-root@1.1.0: resolution: { @@ -6412,11 +6618,12 @@ packages: debug: optional: true - for-each@0.3.3: + for-each@0.3.4: resolution: { - integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==, + integrity: sha512-kKaIINnFpzW6ffJNDjjyjrk21BkDx38c0xa/klsT8VzLCaMEefv4ZTacrcVR4DmgTeBra++jMDAfS/tS799YDw==, } + engines: { node: '>= 0.4' } foreground-child@3.3.0: resolution: @@ -6432,23 +6639,16 @@ packages: } engines: { node: '>= 6' } - forwarded@0.2.0: - resolution: - { - integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==, - } - engines: { node: '>= 0.6' } - fraction.js@4.3.7: resolution: { integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==, } - framer-motion@11.18.0: + framer-motion@11.18.2: resolution: { - integrity: sha512-Vmjl5Al7XqKHzDFnVqzi1H9hzn5w4eN/bdqXTymVpU2UuMQuz9w6UPdsL9dFBeH7loBlnu4qcEXME+nvbkcIOw==, + integrity: sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==, } peerDependencies: '@emotion/is-prop-valid': '*' @@ -6462,13 +6662,6 @@ packages: react-dom: optional: true - fresh@0.5.2: - resolution: - { - integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==, - } - engines: { node: '>= 0.6' } - front-matter@4.0.2: resolution: { @@ -6564,6 +6757,13 @@ packages: } engines: { node: '>= 0.4' } + get-stream@5.2.0: + resolution: + { + integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==, + } + engines: { node: '>=8' } + get-stream@6.0.1: resolution: { @@ -6578,6 +6778,13 @@ packages: } engines: { node: '>= 0.4' } + get-uri@6.0.4: + resolution: + { + integrity: sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==, + } + engines: { node: '>= 14' } + glob-parent@5.1.2: resolution: { @@ -6667,10 +6874,17 @@ packages: } engines: { node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0 } - happy-dom@12.10.3: + gzip-size@6.0.0: resolution: { - integrity: sha512-JzUXOh0wdNGY54oKng5hliuBkq/+aT1V3YpTM+lrN/GoLQTANZsMaIvmHiHe612rauHvPJnDZkZ+5GZR++1Abg==, + integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==, + } + engines: { node: '>=10' } + + has-async-hooks@1.0.0: + resolution: + { + integrity: sha512-YF0VPGjkxr7AyyQQNykX8zK4PvtEDsUJAPqwu06UFz1lb6EvI53sPh5H1kWxg8NXI5LsfRCZ8uX9NkYDZBb/mw==, } has-bigints@1.1.0: @@ -6721,6 +6935,19 @@ packages: } engines: { node: '>= 0.4' } + hdr-histogram-js@3.0.0: + resolution: + { + integrity: sha512-/EpvQI2/Z98mNFYEnlqJ8Ogful8OpArLG/6Tf2bPnkutBVLIeMVNHjk1ZDfshF2BUweipzbk+dB1hgSB7SIakw==, + } + engines: { node: '>=14' } + + hdr-histogram-percentiles-obj@3.0.0: + resolution: + { + integrity: sha512-7kIufnBqdsBGcSZLPJwqHT3yhk1QTsSlFsVD3kx5ixH/AlgBs9yM1q6DPhXZ8f8gtdqgh7N7/5btRLpQsS2gHw==, + } + headers-polyfill@4.0.3: resolution: { @@ -6759,12 +6986,18 @@ packages: integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==, } - http-errors@2.0.0: + http-link-header@1.1.3: resolution: { - integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==, + integrity: sha512-3cZ0SRL8fb9MUlU3mKM61FcQvPfXx2dBrZW3Vbg5CXa8jFlK8OaEpePenLe1oEXQduhz8b0QjsqfS59QP4AJDQ==, + } + engines: { node: '>=6.0.0' } + + http-parser-js@0.5.9: + resolution: + { + integrity: sha512-n1XsPy3rXVxlqxVioEWdC+0+M+SQw0DpJynwtOPo1X+ZlvdzTLtDBIJJlDQTnwZIFJrZSzSGmIOUdP8tu+SgLw==, } - engines: { node: '>= 0.8' } http-proxy-agent@5.0.0: resolution: @@ -6809,12 +7042,11 @@ packages: engines: { node: '>=18' } hasBin: true - iconv-lite@0.4.24: + hyperid@3.3.0: resolution: { - integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==, + integrity: sha512-7qhCVT4MJIoEsNcbhglhdmBKb09QtcmJNiIQGq7js/Khf5FtQQ9bzcAuloeqBeee7XD7JqDeve9KNlQya5tSGQ==, } - engines: { node: '>=0.10.0' } iconv-lite@0.6.3: resolution: @@ -6836,10 +7068,16 @@ packages: } engines: { node: '>= 4' } - immer@9.0.21: + image-ssim@0.2.0: + resolution: + { + integrity: sha512-W7+sO6/yhxy83L0G7xR8YAc5Z5QFtYEXXRV6EaE8tuYBZJnA3gVgp3q7X7muhLZVodeb9UfvjSbwt9VJwjIYAg==, + } + + immediate@3.0.6: resolution: { - integrity: sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==, + integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==, } import-fresh@3.3.0: @@ -6891,12 +7129,18 @@ packages: } engines: { node: '>= 0.4' } - ipaddr.js@1.9.1: + intl-messageformat@10.7.14: resolution: { - integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==, + integrity: sha512-mMGnE4E1otdEutV5vLUdCxRJygHB5ozUBxsPB5qhitewssrS/qGruq9bmvIRkkGsNeK5ZWLfYRld18UHGTIifQ==, } - engines: { node: '>= 0.10' } + + ip-address@9.0.5: + resolution: + { + integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==, + } + engines: { node: '>= 12' } is-array-buffer@3.0.5: resolution: @@ -6917,10 +7161,10 @@ packages: integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==, } - is-async-function@2.1.0: + is-async-function@2.1.1: resolution: { - integrity: sha512-GExz9MtyhlZyXYLxzlJRj5WUCE661zhDa1Yna52CN57AJsymh+DvXXjyveSioqSRdxvUrdKdvqB1b5cVKsNpWQ==, + integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==, } engines: { node: '>= 0.4' } @@ -7057,6 +7301,13 @@ packages: } engines: { node: '>=0.12.0' } + is-obj@2.0.0: + resolution: + { + integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==, + } + engines: { node: '>=8' } + is-path-inside@3.0.3: resolution: { @@ -7064,6 +7315,13 @@ packages: } engines: { node: '>=8' } + is-plain-object@5.0.0: + resolution: + { + integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==, + } + engines: { node: '>=0.10.0' } + is-potential-custom-element-name@1.0.1: resolution: { @@ -7119,6 +7377,12 @@ packages: } engines: { node: '>= 0.4' } + is-typedarray@1.0.0: + resolution: + { + integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==, + } + is-unicode-supported@0.1.0: resolution: { @@ -7474,6 +7738,12 @@ packages: } hasBin: true + jpeg-js@0.4.4: + resolution: + { + integrity: sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==, + } + js-cookie@3.0.5: resolution: { @@ -7481,6 +7751,13 @@ packages: } engines: { node: '>=14' } + js-library-detector@6.7.0: + resolution: + { + integrity: sha512-c80Qupofp43y4cJ7+8TTDN/AsDwLi5oOm/plBrWI+iQt485vKXCco+yVmOwEgdo9VOdsYTuV0UlTeetVPTriXA==, + } + engines: { node: '>=12' } + js-tokens@4.0.0: resolution: { @@ -7501,6 +7778,12 @@ packages: } hasBin: true + jsbn@1.1.0: + resolution: + { + integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==, + } + jsdom@20.0.3: resolution: { @@ -7633,6 +7916,12 @@ packages: integrity: sha512-iuS72HcAYUemsCRQCm4XZzkGhZb8a9KagW+ee2TFfkkf9f3ZpUYSrobMpjYVZRkgMOx7Zk5VCPMxm1nouJTfnQ==, } + lexical@0.27.1: + resolution: + { + integrity: sha512-EXlRE/NQO9quKFuz4Z2pu5f48oF8LZOAw0YpN0RmGQx2CbqmEi7SIgO+d+NUKVx9Qs1vX0DL5S7GJpvpZBOE3w==, + } + lib0@0.2.99: resolution: { @@ -7641,6 +7930,32 @@ packages: engines: { node: '>=16' } hasBin: true + lie@3.1.1: + resolution: + { + integrity: sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==, + } + + lighthouse-logger@2.0.1: + resolution: + { + integrity: sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==, + } + + lighthouse-stack-packs@1.12.2: + resolution: + { + integrity: sha512-Ug8feS/A+92TMTCK6yHYLwaFMuelK/hAKRMdldYkMNwv+d9PtWxjXEg6rwKtsUXTADajhdrhXyuNCJ5/sfmPFw==, + } + + lighthouse@12.3.0: + resolution: + { + integrity: sha512-OaLE8DasnwQkn2CBo2lKtD+IQv42mNP3T+Vaw29I++rAh0Zpgc6SM15usdIYyzhRMR5EWFxze5Fyb+HENJSh2A==, + } + engines: { node: '>=18.16' } + hasBin: true + lilconfig@3.1.3: resolution: { @@ -7661,6 +7976,12 @@ packages: } engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + localforage@1.10.0: + resolution: + { + integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==, + } + locate-path@5.0.0: resolution: { @@ -7675,12 +7996,36 @@ packages: } engines: { node: '>=10' } + lodash-es@4.17.21: + resolution: + { + integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==, + } + + lodash.chunk@4.2.0: + resolution: + { + integrity: sha512-ZzydJKfUHJwHa+hF5X66zLFCBrWn5GeF28OHEr4WVWtNDXlQ/IjWKPBiikqKo2ne0+v6JgCgJ0GzJp8k8bHC7w==, + } + + lodash.clonedeep@4.5.0: + resolution: + { + integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==, + } + lodash.debounce@4.0.8: resolution: { integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==, } + lodash.flatten@4.4.0: + resolution: + { + integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==, + } + lodash.memoize@4.1.2: resolution: { @@ -7712,6 +8057,12 @@ packages: } engines: { node: '>=10' } + lookup-closest-locale@6.2.0: + resolution: + { + integrity: sha512-/c2kL+Vnp1jnV6K6RpDTHK3dgg0Tu2VVp+elEiJpjfS1UyY7AjOYHohRug6wT0OpoX2qFgNORndE9RqesfVxWQ==, + } + loose-envify@1.4.0: resolution: { @@ -7743,18 +8094,18 @@ packages: integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==, } - lru-cache@11.0.2: + lru-cache@5.1.1: resolution: { - integrity: sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==, + integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==, } - engines: { node: 20 || >=22 } - lru-cache@5.1.1: + lru-cache@7.18.3: resolution: { - integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==, + integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==, } + engines: { node: '>=12' } lucide-react@0.460.0: resolution: @@ -7783,6 +8134,13 @@ packages: integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==, } + make-dir@3.1.0: + resolution: + { + integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==, + } + engines: { node: '>=8' } + make-dir@4.0.0: resolution: { @@ -7802,6 +8160,18 @@ packages: integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==, } + manage-path@2.0.0: + resolution: + { + integrity: sha512-NJhyB+PJYTpxhxZJ3lecIGgh4kwIY2RAh44XvAz9UlqthlQwtPBf62uBVR8XaD8CRuSjQ6TnZH2lNJkbLPZM2A==, + } + + marky@1.2.5: + resolution: + { + integrity: sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==, + } + math-intrinsics@1.1.0: resolution: { @@ -7827,19 +8197,6 @@ packages: integrity: sha512-1N4qp+jE0pL5Xv4uEcwVUhIkwdUO3S/9gML90nqKA7v7FcOS5vUtatfzok9S9U1EJU8dHWlcv95WLnKmmxZI9w==, } - media-typer@0.3.0: - resolution: - { - integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==, - } - engines: { node: '>= 0.6' } - - merge-descriptors@1.0.3: - resolution: - { - integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==, - } - merge-stream@2.0.0: resolution: { @@ -7853,12 +8210,11 @@ packages: } engines: { node: '>= 8' } - methods@1.1.2: + metaviewport-parser@0.3.0: resolution: { - integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==, + integrity: sha512-EoYJ8xfjQ6kpe9VbVHvZTZHiOl4HL1Z18CrZ+qahvLXT7ZO4YTC2JMyt5FaUp9JJp6J4Ybb/z7IsCXZt86/QkQ==, } - engines: { node: '>= 0.6' } micromatch@4.0.8: resolution: @@ -7881,14 +8237,6 @@ packages: } engines: { node: '>= 0.6' } - mime@1.6.0: - resolution: - { - integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==, - } - engines: { node: '>=4' } - hasBin: true - mimic-fn@2.1.0: resolution: { @@ -7955,22 +8303,22 @@ packages: integrity: sha512-sEKPVl2rM+MNVkGQt3ChdmD8YsigmXdn5NifZn6jiwn9LRJpWm8F3guhaqrJT/JOat6pwpbXEk6kv+b9DMIjsQ==, } - motion-dom@11.16.4: + motion-dom@11.18.1: resolution: { - integrity: sha512-2wuCie206pCiP2K23uvwJeci4pMFfyQKpWI0Vy6HrCTDzDCer4TsYtT7IVnuGbDeoIV37UuZiUr6SZMHEc1Vww==, + integrity: sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==, } - motion-utils@11.16.0: + motion-utils@11.18.1: resolution: { - integrity: sha512-ngdWPjg31rD4WGXFi0eZ00DQQqKKu04QExyv/ymlC+3k+WIgYVFbt6gS5JsFPbJODTF/r8XiE/X+SsoT9c0ocw==, + integrity: sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==, } - motion@11.18.0: + motion@11.18.2: resolution: { - integrity: sha512-uJ4zNXh/4K9C5wftxHKlXLHC0Rc9dHSHPyO1P6T9XE2bTn2z8C2lOZX/M8vAmFp0gtJTJ3aYkv44lTtJSfv6+A==, + integrity: sha512-JLjvFDuFr42NFtcVoMAyC2sEjnpA8xpy6qWPyzQvCloznAyQ8FIXioxWfHiLtgYhoVpfUqSWpn1h9++skj9+Wg==, } peerDependencies: '@emotion/is-prop-valid': '*' @@ -8051,17 +8399,17 @@ packages: integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==, } - negotiator@0.6.3: + netmask@2.0.2: resolution: { - integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==, + integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==, } - engines: { node: '>= 0.6' } + engines: { node: '>= 0.4.0' } - next@15.1.4: + next@15.1.6: resolution: { - integrity: sha512-mTaq9dwaSuwwOrcu3ebjDYObekkxRnXpuVL21zotM8qE2W0HBOdVIdg2Li9QjMEZrj73LN96LcWcz62V19FjAg==, + integrity: sha512-Hch4wzbaX0vKQtalpXvUiw5sYivBy4cm5rzUKrBnUB/y436LGrvOUqYvlSeNVCWFO/770gDlltR9gqZH62ct4Q==, } engines: { node: ^18.18.0 || ^19.8.0 || >= 20.0.0 } hasBin: true @@ -8082,6 +8430,16 @@ packages: sass: optional: true + nextjs-toploader@3.7.15: + resolution: + { + integrity: sha512-DvvXEJVRPfE2j1HVXgFhmPl8pRcLb/4mvyVBDuYdMdkbEY7KJghp0fG5iOZ002cV6awbBw9j/Di7vQL8LRazxQ==, + } + peerDependencies: + next: '>= 6.0.0' + react: '>= 16.0.0' + react-dom: '>= 16.0.0' + no-case@3.0.4: resolution: { @@ -8127,32 +8485,17 @@ packages: } engines: { node: '>=8' } - nth-check@2.1.1: + nprogress@0.2.0: resolution: { - integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==, + integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==, } - nuqs@2.3.0: + nth-check@2.1.1: resolution: { - integrity: sha512-ChS56bJZdaTQzCJb6jPel6cIHYh8/V/GSIjZoIe5yAssGdcrVaBFBgzHfJW6IewbR6yc1Zch2CmGsdgztR+xmA==, + integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==, } - peerDependencies: - '@remix-run/react': '>=2' - next: '>=14.2.0' - react: '>=18.2.0 || ^19.0.0-0' - react-router: ^7 - react-router-dom: ^6 || ^7 - peerDependenciesMeta: - '@remix-run/react': - optional: true - next: - optional: true - react-router: - optional: true - react-router-dom: - optional: true nwsapi@2.2.16: resolution: @@ -8160,10 +8503,10 @@ packages: integrity: sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==, } - nx@20.3.1: + nx@20.3.3: resolution: { - integrity: sha512-pO48DoQAwVKBEF7/od3bc1tHBYfafgiuS/hHX3yGmhpWW58baIlxMWFp6QY9+A9Q0R+26pd6AEGnE7d1f7+i/g==, + integrity: sha512-IUu2D8/bVa7aSr3ViRcrmpTGO2FKqzJoio6gjeq/YbyUHyjrrq5HUmHFx30Wm2vmC1BGm0MeyakTNUJzQvfAog==, } hasBin: true peerDependencies: @@ -8231,12 +8574,12 @@ packages: } engines: { node: '>= 0.4' } - on-finished@2.4.1: + on-net-listen@1.1.2: resolution: { - integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==, + integrity: sha512-y1HRYy8s/RlcBvDUwKXSmkODMdx4KSuIvloCnQYJ2LdBBC1asY4HtfhXwe3UWknLakATZDnbzht2Ijw3M1EqFg==, } - engines: { node: '>= 0.8' } + engines: { node: '>=9.4.0 || ^8.9.4' } once@1.4.0: resolution: @@ -8258,6 +8601,13 @@ packages: } engines: { node: '>=12' } + opener@1.5.2: + resolution: + { + integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==, + } + hasBin: true + optionator@0.9.4: resolution: { @@ -8320,12 +8670,32 @@ packages: } engines: { node: '>=6' } + pac-proxy-agent@7.1.0: + resolution: + { + integrity: sha512-Z5FnLVVZSnX7WjBg0mhDtydeRZ1xMcATZThjySQUHqr+0ksP8kqaw23fNKkaaN/Z8gwLUs/W7xdl0I75eP2Xyw==, + } + engines: { node: '>= 14' } + + pac-resolver@7.0.1: + resolution: + { + integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==, + } + engines: { node: '>= 14' } + package-json-from-dist@1.0.1: resolution: { integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==, } + pako@1.0.11: + resolution: + { + integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==, + } + parent-module@1.0.1: resolution: { @@ -8333,6 +8703,12 @@ packages: } engines: { node: '>=6' } + parse-cache-control@1.0.1: + resolution: + { + integrity: sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==, + } + parse-json@5.2.0: resolution: { @@ -8346,13 +8722,6 @@ packages: integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==, } - parseurl@1.3.3: - resolution: - { - integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==, - } - engines: { node: '>= 0.8' } - path-exists@4.0.0: resolution: { @@ -8387,12 +8756,6 @@ packages: } engines: { node: '>=16 || 14 >=14.18' } - path-to-regexp@0.1.12: - resolution: - { - integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==, - } - path-to-regexp@6.3.0: resolution: { @@ -8419,6 +8782,12 @@ packages: } engines: { node: '>= 14.16' } + pend@1.2.0: + resolution: + { + integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==, + } + picocolors@1.1.1: resolution: { @@ -8460,18 +8829,18 @@ packages: } engines: { node: '>=8' } - playwright-core@1.49.1: + playwright-core@1.50.0: resolution: { - integrity: sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==, + integrity: sha512-CXkSSlr4JaZs2tZHI40DsZUN/NIwgaUPsyLuOAaIZp2CyF2sN5MM5NJsyB188lFSSozFxQ5fPT4qM+f0tH/6wQ==, } engines: { node: '>=18' } hasBin: true - playwright@1.49.1: + playwright@1.50.0: resolution: { - integrity: sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==, + integrity: sha512-+GinGfGTrd2IfX1TA4N2gNmeIksSb+IAe589ZH+FlmpV3MYTx6+buChGIuDLQwrGNCw2lWibqV50fU510N7S+w==, } engines: { node: '>=18' } hasBin: true @@ -8545,10 +8914,10 @@ packages: } engines: { node: ^10 || ^12 || >=14 } - postcss@8.5.0: + postcss@8.5.1: resolution: { - integrity: sha512-27VKOqrYfPncKA2NrFOVhP5MGAfHKLYn/Q0mz9cNQyRAKYi3VNHwYU2qKKqPCqgBmeeJ0uAFB56NumXZ5ZReXg==, + integrity: sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==, } engines: { node: ^10 || ^12 || >=14 } @@ -8574,6 +8943,13 @@ packages: engines: { node: '>=14' } hasBin: true + pretty-bytes@5.6.0: + resolution: + { + integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==, + } + engines: { node: '>=6' } + pretty-format@27.5.1: resolution: { @@ -8595,6 +8971,13 @@ packages: } engines: { node: '>=6' } + progress@2.0.3: + resolution: + { + integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==, + } + engines: { node: '>=0.4.0' } + prompts@2.4.2: resolution: { @@ -8608,12 +8991,12 @@ packages: integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==, } - proxy-addr@2.0.7: + proxy-agent@6.5.0: resolution: { - integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==, + integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==, } - engines: { node: '>= 0.10' } + engines: { node: '>= 14' } proxy-from-env@1.1.0: resolution: @@ -8627,6 +9010,12 @@ packages: integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==, } + pump@3.0.2: + resolution: + { + integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==, + } + punycode@2.3.1: resolution: { @@ -8634,25 +9023,33 @@ packages: } engines: { node: '>=6' } - pure-rand@6.1.0: + puppeteer-core@23.11.1: resolution: { - integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==, + integrity: sha512-3HZ2/7hdDKZvZQ7dhhITOUg4/wOrDRjyK2ZBllRB0ZCOi9u0cwq1ACHDjBB+nX+7+kltHjQvBRdeY7+W0T+7Gg==, + } + engines: { node: '>=18' } + + puppeteer-core@24.1.1: + resolution: + { + integrity: sha512-7FF3gq6bpIsbq3I8mfbodXh3DCzXagoz3l2eGv1cXooYU4g0P4mcHQVHuBD4iSZPXNg8WjzlP5kmRwK9UvwF0A==, } + engines: { node: '>=18' } - qs@6.13.0: + puppeteer@24.1.1: resolution: { - integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==, + integrity: sha512-fuhceZ5HZuDXVuaMIRxUuDHfCJLmK0pXh8FlzVQ0/+OApStevxZhU5kAVeYFOEqeCF5OoAyZjcWbdQK27xW/9A==, } - engines: { node: '>=0.6' } + engines: { node: '>=18' } + hasBin: true - query-string@9.1.1: + pure-rand@6.1.0: resolution: { - integrity: sha512-MWkCOVIcJP9QSKU52Ngow6bsAWAPlPK2MludXvcrS2bGZSl+T1qX9MZvRIkqUIkGLJquMJHWfsT6eRqUpp4aWg==, + integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==, } - engines: { node: '>=18' } querystringify@2.2.0: resolution: @@ -8666,27 +9063,13 @@ packages: integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==, } - range-parser@1.2.1: - resolution: - { - integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==, - } - engines: { node: '>= 0.6' } - - raw-body@2.5.2: - resolution: - { - integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==, - } - engines: { node: '>= 0.8' } - - react-dom@18.3.1: + react-dom@19.0.0: resolution: { - integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==, + integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==, } peerDependencies: - react: ^18.3.1 + react: ^19.0.0 react-error-boundary@3.1.4: resolution: @@ -8712,22 +9095,10 @@ packages: peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 - react-intersection-observer@9.14.1: - resolution: - { - integrity: sha512-k1xIUn3sCQi3ugNeF64FJb3zwve5mcetvAUR9JazXeOmtap4IP2evN8rs+yf6SQ7F1QydsOGiqTmt+lySKZ9uA==, - } - peerDependencies: - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - react-dom: - optional: true - - react-intersection-observer@9.15.0: + react-intersection-observer@9.15.1: resolution: { - integrity: sha512-qul9TzGgZtHIHAsLOXnRfMWNYCrqjU87HMKhRjwC8l6XSxz2Bo0xmpq5pklaXGj+brx2gSMe8lp1K17mMP2Q8w==, + integrity: sha512-vGrqYEVWXfH+AGu241uzfUpNK4HAdhCkSAyFdkMb9VWWXs6mxzBLpWCxEy9YcnDNY2g9eO6z7qUtTBdA9hc8pA==, } peerDependencies: react: ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -8783,10 +9154,10 @@ packages: '@types/react': optional: true - react-remove-scroll@2.6.2: + react-remove-scroll@2.6.3: resolution: { - integrity: sha512-KmONPx5fnlXYJQqC62Q+lwIeAk64ws/cUw6omIumRzMRPqgnYqhSSti99nbj0Ry13bv7dF+BKn7NB+OqkdZGTw==, + integrity: sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ==, } engines: { node: '>=10' } peerDependencies: @@ -8796,29 +9167,6 @@ packages: '@types/react': optional: true - react-router-dom@7.1.1: - resolution: - { - integrity: sha512-vSrQHWlJ5DCfyrhgo0k6zViOe9ToK8uT5XGSmnuC2R3/g261IdIMpZVqfjD6vWSXdnf5Czs4VA/V60oVR6/jnA==, - } - engines: { node: '>=20.0.0' } - peerDependencies: - react: '>=18' - react-dom: '>=18' - - react-router@7.1.1: - resolution: - { - integrity: sha512-39sXJkftkKWRZ2oJtHhCxmoCrBCULr/HAH4IT5DHlgu/Q0FCPV0S4Lx+abjDTx/74xoZzNYDYbOZWlJjruyuDQ==, - } - engines: { node: '>=20.0.0' } - peerDependencies: - react: '>=18' - react-dom: '>=18' - peerDependenciesMeta: - react-dom: - optional: true - react-style-singleton@2.2.3: resolution: { @@ -8840,6 +9188,18 @@ packages: peerDependencies: react: ^16.8.3 || ^17 || ^18 || ^19.0.0 || ^19.0.0-rc + react-use-measure@2.1.7: + resolution: + { + integrity: sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==, + } + peerDependencies: + react: '>=16.13' + react-dom: '>=16.13' + peerDependenciesMeta: + react-dom: + optional: true + react-zoom-pan-pinch@3.6.0: resolution: { @@ -8850,10 +9210,10 @@ packages: react: '*' react-dom: '*' - react@18.3.1: + react@19.0.0: resolution: { - integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==, + integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==, } engines: { node: '>=0.10.0' } @@ -8943,6 +9303,12 @@ packages: } hasBin: true + reinterval@1.1.0: + resolution: + { + integrity: sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ==, + } + require-directory@2.1.1: resolution: { @@ -9006,6 +9372,12 @@ packages: } engines: { node: '>=8' } + retimer@3.0.0: + resolution: + { + integrity: sha512-WKE0j11Pa0ZJI5YIk0nflGI7SQsfl2ljihVy7ogh7DeQSeYAUi0ubZ/yEueGtDfUPk6GH5LRw1hBdLq4IwUBWA==, + } + reusify@1.0.4: resolution: { @@ -9021,10 +9393,33 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true - rollup@4.30.1: + robots-parser@3.0.1: + resolution: + { + integrity: sha512-s+pyvQeIKIZ0dx5iJiQk1tPLJAWln39+MI5jtM8wnyws+G5azk+dMnMX0qfbqNetKKNgcWWOdi0sfm+FbQbgdQ==, + } + engines: { node: '>=10.0.0' } + + rollup-plugin-visualizer@5.14.0: + resolution: + { + integrity: sha512-VlDXneTDaKsHIw8yzJAFWtrzguoJ/LnQ+lMpoVfYJ3jJF4Ihe5oYLAqLklIK/35lgUY+1yEzCkHyZ1j4A5w5fA==, + } + engines: { node: '>=18' } + hasBin: true + peerDependencies: + rolldown: 1.x + rollup: 2.x || 3.x || 4.x + peerDependenciesMeta: + rolldown: + optional: true + rollup: + optional: true + + rollup@4.32.0: resolution: { - integrity: sha512-mlJ4glW020fPuLi7DkM/lN97mYEZGWeqBnrljzN0gs7GLctqX3lNWxKQ7Gl712UAX+6fog/L3jh4gb7R6aVi3w==, + integrity: sha512-JmrhfQR31Q4AuNBjjAX4s+a/Pu/Q8Q9iwjWBsjRH1q52SPFE2NqRMK6fUZKKnvKO6id+h7JIRf0oYsph53eATg==, } engines: { node: '>=18.0.0', npm: '>=8.0.0' } hasBin: true @@ -9087,11 +9482,18 @@ packages: } engines: { node: '>=v12.22.7' } - scheduler@0.23.2: + scheduler@0.25.0: + resolution: + { + integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==, + } + + semver@5.7.2: resolution: { - integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==, + integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==, } + hasBin: true semver@6.3.1: resolution: @@ -9108,26 +9510,6 @@ packages: engines: { node: '>=10' } hasBin: true - send@0.19.0: - resolution: - { - integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==, - } - engines: { node: '>= 0.8.0' } - - serve-static@1.16.2: - resolution: - { - integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==, - } - engines: { node: '>= 0.8.0' } - - set-cookie-parser@2.7.1: - resolution: - { - integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==, - } - set-function-length@1.2.2: resolution: { @@ -9149,12 +9531,6 @@ packages: } engines: { node: '>= 0.4' } - setprototypeof@1.2.0: - resolution: - { - integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==, - } - sharp@0.33.5: resolution: { @@ -9229,6 +9605,13 @@ packages: integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==, } + sirv@2.0.4: + resolution: + { + integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==, + } + engines: { node: '>= 10' } + sirv@3.0.0: resolution: { @@ -9249,12 +9632,33 @@ packages: } engines: { node: '>=8' } + smart-buffer@4.2.0: + resolution: + { + integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==, + } + engines: { node: '>= 6.0.0', npm: '>= 3.0.0' } + snake-case@3.0.4: resolution: { integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==, } + socks-proxy-agent@8.0.5: + resolution: + { + integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==, + } + engines: { node: '>= 14' } + + socks@2.8.3: + resolution: + { + integrity: sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==, + } + engines: { node: '>= 10.0.0', npm: '>= 3.0.0' } + source-map-js@1.2.1: resolution: { @@ -9268,12 +9672,6 @@ packages: integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==, } - source-map-support@0.5.21: - resolution: - { - integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==, - } - source-map@0.5.7: resolution: { @@ -9288,12 +9686,19 @@ packages: } engines: { node: '>=0.10.0' } - split-on-first@3.0.0: + source-map@0.7.4: resolution: { - integrity: sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==, + integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==, } - engines: { node: '>=12' } + engines: { node: '>= 8' } + + speedline-core@1.4.3: + resolution: + { + integrity: sha512-DI7/OuAUD+GMpR6dmu8lliO2Wg5zfeh+/xsdyJZCzd8o5JgFUjCeLsBDuZjIQJdwXS3J0L/uZYrELKYqx+PXog==, + } + engines: { node: '>=8.0' } sprintf-js@1.0.3: resolution: @@ -9301,6 +9706,12 @@ packages: integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==, } + sprintf-js@1.1.3: + resolution: + { + integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==, + } + stack-utils@2.0.6: resolution: { @@ -9334,6 +9745,12 @@ packages: } engines: { node: '>=10.0.0' } + streamx@2.22.0: + resolution: + { + integrity: sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==, + } + strict-event-emitter@0.5.1: resolution: { @@ -9541,14 +9958,6 @@ packages: integrity: sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==, } - tailwindcss-animate@1.0.7: - resolution: - { - integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==, - } - peerDependencies: - tailwindcss: '>=3.0.0 || insiders' - tailwindcss@3.4.17: resolution: { @@ -9557,6 +9966,12 @@ packages: engines: { node: '>=14.0.0' } hasBin: true + tar-fs@3.0.8: + resolution: + { + integrity: sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg==, + } + tar-stream@2.2.0: resolution: { @@ -9564,13 +9979,11 @@ packages: } engines: { node: '>=6' } - terser@5.37.0: + tar-stream@3.1.7: resolution: { - integrity: sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==, + integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==, } - engines: { node: '>=10' } - hasBin: true test-exclude@6.0.0: resolution: @@ -9586,6 +9999,12 @@ packages: } engines: { node: '>=18' } + text-decoder@1.2.3: + resolution: + { + integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==, + } + text-table@0.2.0: resolution: { @@ -9605,6 +10024,31 @@ packages: integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==, } + third-party-web@0.26.2: + resolution: + { + integrity: sha512-taJ0Us0lKoYBqcbccMuDElSUPOxmBfwlHe1OkHQ3KFf+RwovvBHdXhbFk9XJVQE2vHzxbTwvwg5GFsT9hbDokQ==, + } + + third-party-web@0.26.6: + resolution: + { + integrity: sha512-GsjP92xycMK8qLTcQCacgzvffYzEqe29wyz3zdKVXlfRD5Kz1NatCTOZEeDaSd6uCZXvGd2CNVtQ89RNIhJWvA==, + } + + through@2.3.8: + resolution: + { + integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==, + } + + timestring@6.0.0: + resolution: + { + integrity: sha512-wMctrWD2HZZLuIlchlkE2dfXJh7J2KDI9Dwl+2abPYg0mswQHfOAyQW3jJg1pY5VfttSINZuKcXoB3FGypVklA==, + } + engines: { node: '>=8' } + tinybench@2.9.0: resolution: { @@ -9645,16 +10089,28 @@ packages: } engines: { node: '>=14.0.0' } - tldts-core@6.1.71: + tldts-core@6.1.75: + resolution: + { + integrity: sha512-AOvV5YYIAFFBfransBzSTyztkc3IMfz5Eq3YluaRiEu55nn43Fzaufx70UqEKYr8BoLCach4q8g/bg6e5+/aFw==, + } + + tldts-core@6.1.76: + resolution: + { + integrity: sha512-uzhJ02RaMzgQR3yPoeE65DrcHI6LoM4saUqXOt/b5hmb3+mc4YWpdSeAQqVqRUlQ14q8ZuLRWyBR1ictK1dzzg==, + } + + tldts-icann@6.1.76: resolution: { - integrity: sha512-LRbChn2YRpic1KxY+ldL1pGXN/oVvKfCVufwfVzEQdFYNo39uF7AJa/WXdo+gYO7PTvdfkCPCed6Hkvz/kR7jg==, + integrity: sha512-RgyLN7i/wCF3dZCWi2Qwmk68lNnPjBanLDnn0aLqAAe0ZLMr7+j3BYIzNJ7+bc5XuqZ6lATnRwlH52gSQiKOXw==, } - tldts@6.1.71: + tldts@6.1.75: resolution: { - integrity: sha512-LQIHmHnuzfZgZWAf2HzL83TIIrD8NhhI0DVxqo9/FdOd4ilec+NTNZOlDZf7EwrTNoutccbsHjvWHYXLAtvxjw==, + integrity: sha512-+lFzEXhpl7JXgWYaXcB6DqTYXbUArvrWAE/5ioq/X3CdWLbDjpPP4XTrQBmEJ91y3xbe4Fkw7Lxv4P3GWeJaNg==, } hasBin: true @@ -9685,13 +10141,6 @@ packages: } engines: { node: '>=8.0' } - toidentifier@1.0.1: - resolution: - { - integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==, - } - engines: { node: '>=0.6' } - totalist@3.0.1: resolution: { @@ -9782,12 +10231,6 @@ packages: integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==, } - turbo-stream@2.4.0: - resolution: - { - integrity: sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==, - } - type-check@0.4.0: resolution: { @@ -9816,20 +10259,13 @@ packages: } engines: { node: '>=10' } - type-fest@4.32.0: + type-fest@4.33.0: resolution: { - integrity: sha512-rfgpoi08xagF3JSdtJlCwMq9DGNDE0IMh3Mkpc1wUypg9vPi786AiqeBBKcqvIkq42azsBM85N490fyZjeUftw==, + integrity: sha512-s6zVrxuyKbbAsSAD5ZPTB77q4YIdRctkTbJ2/Dqlinwz+8ooH2gd+YA7VA6Pa93KML9GockVvoxjZ2vHP+mu8g==, } engines: { node: '>=16' } - type-is@1.6.18: - resolution: - { - integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==, - } - engines: { node: '>= 0.6' } - typed-array-buffer@1.0.3: resolution: { @@ -9858,10 +10294,22 @@ packages: } engines: { node: '>= 0.4' } - typescript@5.4.5: + typed-query-selector@2.12.0: + resolution: + { + integrity: sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==, + } + + typedarray-to-buffer@3.1.5: resolution: { - integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==, + integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==, + } + + typescript@5.7.3: + resolution: + { + integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==, } engines: { node: '>=14.17' } hasBin: true @@ -9873,6 +10321,12 @@ packages: } engines: { node: '>= 0.4' } + unbzip2-stream@1.4.3: + resolution: + { + integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==, + } + undici-types@5.26.5: resolution: { @@ -9907,19 +10361,19 @@ packages: } engines: { node: '>=4' } - universalify@0.2.0: + unique-string@2.0.0: resolution: { - integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==, + integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==, } - engines: { node: '>= 4.0.0' } + engines: { node: '>=8' } - unpipe@1.0.0: + universalify@0.2.0: resolution: { - integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==, + integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==, } - engines: { node: '>= 0.8' } + engines: { node: '>= 4.0.0' } update-browserslist-db@1.1.2: resolution: @@ -9975,40 +10429,38 @@ packages: '@types/react': optional: true - use-sync-external-store@1.4.0: + util-deprecate@1.0.2: resolution: { - integrity: sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==, + integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==, } - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - util-deprecate@1.0.2: + uuid-parse@1.1.0: resolution: { - integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==, + integrity: sha512-OdmXxA8rDsQ7YpNVbKSJkNzTw2I+S5WsbMDnCtIWSQaosNAcWtFuI/YK1TjzUI6nbkgiqEyh8gWngfcv8Asd9A==, } - utils-merge@1.0.1: + uuid@11.1.0: resolution: { - integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==, + integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==, } - engines: { node: '>= 0.4.0' } + hasBin: true - v8-to-istanbul@9.3.0: + uuid@8.3.2: resolution: { - integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==, + integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==, } - engines: { node: '>=10.12.0' } + hasBin: true - vary@1.1.2: + v8-to-istanbul@9.3.0: resolution: { - integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==, + integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==, } - engines: { node: '>= 0.8' } + engines: { node: '>=10.12.0' } vaul@1.1.2: resolution: @@ -10019,6 +10471,13 @@ packages: react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc + vite-bundle-analyzer@0.16.1: + resolution: + { + integrity: sha512-3JtgXfu205taefMK1QZiEFYxCaBFJIkxuX71Eb7qbSneZUJNIat2u5DqBIVP4jZ5AK8G5QXXvQsSmlkFeazZGA==, + } + hasBin: true + vite-node@2.1.8: resolution: { @@ -10027,6 +10486,15 @@ packages: engines: { node: ^18.0.0 || >=20.0.0 } hasBin: true + vite-plugin-image-optimizer@1.1.8: + resolution: + { + integrity: sha512-40bYRDHQLUOrIwJIJQqyKJHrfgVshqzDLtMy8SEgf+fB7PnppslSTTkY7PJFrBGqgbCdOdN9KkqsvccXmnEa5Q==, + } + engines: { node: '>=14' } + peerDependencies: + vite: '>=3' + vite-plugin-svgr@4.3.0: resolution: { @@ -10035,10 +10503,10 @@ packages: peerDependencies: vite: '>=2.6.0' - vite@5.4.11: + vite@5.4.14: resolution: { - integrity: sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==, + integrity: sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==, } engines: { node: ^18.0.0 || >=20.0.0 } hasBin: true @@ -10069,10 +10537,10 @@ packages: terser: optional: true - vite@6.0.7: + vite@6.0.11: resolution: { - integrity: sha512-RDt8r/7qx9940f8FcOIAH9PTViRrghKaK2K1jY3RaAURrEUbm9Du1mJ72G+jlhtG3WwodnfzY8ORQZbBavZEAQ==, + integrity: sha512-4VL9mQPKoHy4+FE0NnRE/kbY51TOfaknxAjt3fJbGJxhIpBZiqVzlZDEesWWsuREXHwNdAoOFZ9MkPEVXczHwg==, } engines: { node: ^18.0.0 || ^20.0.0 || >=22.0.0 } hasBin: true @@ -10173,6 +10641,14 @@ packages: } engines: { node: '>=12' } + webpack-bundle-analyzer@4.10.1: + resolution: + { + integrity: sha512-s3P7pgexgT/HTUSYgxJyn28A+99mmLq4HsJepMPzu0R8ImJc52QNqaFYW1Z2z2uIb1/J3eYgaAWVpaC+v/1aAQ==, + } + engines: { node: '>= 10.13.0' } + hasBin: true + whatwg-encoding@2.0.0: resolution: { @@ -10293,6 +10769,12 @@ packages: integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==, } + write-file-atomic@3.0.3: + resolution: + { + integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==, + } + write-file-atomic@4.0.2: resolution: { @@ -10300,6 +10782,21 @@ packages: } engines: { node: ^12.13.0 || ^14.15.0 || >=16.0.0 } + ws@7.5.10: + resolution: + { + integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==, + } + engines: { node: '>=8.3.0' } + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + ws@8.18.0: resolution: { @@ -10315,6 +10812,13 @@ packages: utf-8-validate: optional: true + xdg-basedir@4.0.0: + resolution: + { + integrity: sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==, + } + engines: { node: '>=8' } + xml-name-validator@4.0.0: resolution: { @@ -10377,10 +10881,16 @@ packages: } engines: { node: '>=12' } - yjs@13.6.22: + yauzl@2.10.0: + resolution: + { + integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==, + } + + yjs@13.6.23: resolution: { - integrity: sha512-+mJxdbmitioqqsql1Zro4dqT3t9HgmW4dxlPtkcsKFJhXSAMyk3lwawhQFxZjj2upJXzhrTUDsaDkZgJWnv3NA==, + integrity: sha512-ExtnT5WIOVpkL56bhLeisG/N5c4fmzKn4k0ROVfJa5TY2QHbH7F0Wu2T5ZhR7ErsFWQEFafyrnSI8TPKVF9Few==, } engines: { node: '>=16.0.0', npm: '>=8.0.0' } @@ -10398,6 +10908,12 @@ packages: } engines: { node: '>=18' } + zod@3.23.8: + resolution: + { + integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==, + } + zod@3.24.1: resolution: { @@ -10435,13 +10951,15 @@ snapshots: '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 - '@asamuzakjp/css-color@2.8.2': + '@asamuzakjp/css-color@2.8.3': dependencies: '@csstools/css-calc': 2.1.1(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) '@csstools/css-color-parser': 3.0.7(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) '@csstools/css-tokenizer': 3.0.3 - lru-cache: 11.0.2 + lru-cache: 10.4.3 + + '@assemblyscript/loader@0.19.23': {} '@babel/code-frame@7.26.2': dependencies: @@ -10451,18 +10969,18 @@ snapshots: '@babel/compat-data@7.26.5': {} - '@babel/core@7.26.0': + '@babel/core@7.26.7': dependencies: '@ampproject/remapping': 2.3.0 '@babel/code-frame': 7.26.2 '@babel/generator': 7.26.5 '@babel/helper-compilation-targets': 7.26.5 - '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) - '@babel/helpers': 7.26.0 - '@babel/parser': 7.26.5 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.7) + '@babel/helpers': 7.26.7 + '@babel/parser': 7.26.7 '@babel/template': 7.25.9 - '@babel/traverse': 7.26.5 - '@babel/types': 7.26.5 + '@babel/traverse': 7.26.7 + '@babel/types': 7.26.7 convert-source-map: 2.0.0 debug: 4.4.0 gensync: 1.0.0-beta.2 @@ -10479,15 +10997,15 @@ snapshots: '@babel/generator@7.26.5': dependencies: - '@babel/parser': 7.26.5 - '@babel/types': 7.26.5 + '@babel/parser': 7.26.7 + '@babel/types': 7.26.7 '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 jsesc: 3.1.0 '@babel/helper-annotate-as-pure@7.25.9': dependencies: - '@babel/types': 7.26.5 + '@babel/types': 7.26.7 '@babel/helper-compilation-targets@7.26.5': dependencies: @@ -10497,29 +11015,29 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.25.9(@babel/core@7.26.0)': + '@babel/helper-create-class-features-plugin@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-annotate-as-pure': 7.25.9 '@babel/helper-member-expression-to-functions': 7.25.9 '@babel/helper-optimise-call-expression': 7.25.9 - '@babel/helper-replace-supers': 7.26.5(@babel/core@7.26.0) + '@babel/helper-replace-supers': 7.26.5(@babel/core@7.26.7) '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 - '@babel/traverse': 7.26.5 + '@babel/traverse': 7.26.7 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/helper-create-regexp-features-plugin@7.26.3(@babel/core@7.26.0)': + '@babel/helper-create-regexp-features-plugin@7.26.3(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-annotate-as-pure': 7.25.9 regexpu-core: 6.2.0 semver: 6.3.1 - '@babel/helper-define-polyfill-provider@0.6.3(@babel/core@7.26.0)': + '@babel/helper-define-polyfill-provider@0.6.3(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-compilation-targets': 7.26.5 '@babel/helper-plugin-utils': 7.26.5 debug: 4.4.0 @@ -10530,74 +11048,74 @@ snapshots: '@babel/helper-environment-visitor@7.24.7': dependencies: - '@babel/types': 7.26.5 + '@babel/types': 7.26.7 '@babel/helper-function-name@7.24.7': dependencies: '@babel/template': 7.25.9 - '@babel/types': 7.26.5 + '@babel/types': 7.26.7 '@babel/helper-hoist-variables@7.24.7': dependencies: - '@babel/types': 7.26.5 + '@babel/types': 7.26.7 '@babel/helper-member-expression-to-functions@7.25.9': dependencies: - '@babel/traverse': 7.26.5 - '@babel/types': 7.26.5 + '@babel/traverse': 7.26.7 + '@babel/types': 7.26.7 transitivePeerDependencies: - supports-color '@babel/helper-module-imports@7.25.9': dependencies: - '@babel/traverse': 7.26.5 - '@babel/types': 7.26.5 + '@babel/traverse': 7.26.7 + '@babel/types': 7.26.7 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0)': + '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-module-imports': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 - '@babel/traverse': 7.26.5 + '@babel/traverse': 7.26.7 transitivePeerDependencies: - supports-color '@babel/helper-optimise-call-expression@7.25.9': dependencies: - '@babel/types': 7.26.5 + '@babel/types': 7.26.7 '@babel/helper-plugin-utils@7.26.5': {} - '@babel/helper-remap-async-to-generator@7.25.9(@babel/core@7.26.0)': + '@babel/helper-remap-async-to-generator@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-annotate-as-pure': 7.25.9 '@babel/helper-wrap-function': 7.25.9 - '@babel/traverse': 7.26.5 + '@babel/traverse': 7.26.7 transitivePeerDependencies: - supports-color - '@babel/helper-replace-supers@7.26.5(@babel/core@7.26.0)': + '@babel/helper-replace-supers@7.26.5(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-member-expression-to-functions': 7.25.9 '@babel/helper-optimise-call-expression': 7.25.9 - '@babel/traverse': 7.26.5 + '@babel/traverse': 7.26.7 transitivePeerDependencies: - supports-color '@babel/helper-skip-transparent-expression-wrappers@7.25.9': dependencies: - '@babel/traverse': 7.26.5 - '@babel/types': 7.26.5 + '@babel/traverse': 7.26.7 + '@babel/types': 7.26.7 transitivePeerDependencies: - supports-color '@babel/helper-split-export-declaration@7.24.7': dependencies: - '@babel/types': 7.26.5 + '@babel/types': 7.26.7 '@babel/helper-string-parser@7.25.9': {} @@ -10608,633 +11126,633 @@ snapshots: '@babel/helper-wrap-function@7.25.9': dependencies: '@babel/template': 7.25.9 - '@babel/traverse': 7.26.5 - '@babel/types': 7.26.5 + '@babel/traverse': 7.26.7 + '@babel/types': 7.26.7 transitivePeerDependencies: - supports-color - '@babel/helpers@7.26.0': + '@babel/helpers@7.26.7': dependencies: '@babel/template': 7.25.9 - '@babel/types': 7.26.5 + '@babel/types': 7.26.7 - '@babel/parser@7.26.5': + '@babel/parser@7.26.7': dependencies: - '@babel/types': 7.26.5 + '@babel/types': 7.26.7 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/traverse': 7.26.5 + '@babel/traverse': 7.26.7 transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 - '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.26.7) transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/traverse': 7.26.5 + '@babel/traverse': 7.26.7 transitivePeerDependencies: - supports-color - '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.26.0)': + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.26.0)': + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.26.0)': + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.26.0)': + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.26.0)': + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-import-assertions@7.26.0(@babel/core@7.26.0)': + '@babel/plugin-syntax-import-assertions@7.26.0(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.26.0)': + '@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.26.0)': + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.26.0)': + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.26.0)': + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.26.0)': + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.26.0)': + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.26.0)': + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.26.0)': + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.26.0)': + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.26.0)': + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.26.0)': + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.26.0)': + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.0) + '@babel/core': 7.26.7 + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.7) '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-arrow-functions@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-arrow-functions@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-async-generator-functions@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-async-generator-functions@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.26.0) - '@babel/traverse': 7.26.5 + '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.26.7) + '@babel/traverse': 7.26.7 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-async-to-generator@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-async-to-generator@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-module-imports': 7.25.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.26.0) + '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.26.7) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-block-scoped-functions@7.26.5(@babel/core@7.26.0)': + '@babel/plugin-transform-block-scoped-functions@7.26.5(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-block-scoping@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-block-scoping@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-class-properties@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-class-properties@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/core': 7.26.7 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.7) '@babel/helper-plugin-utils': 7.26.5 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-class-static-block@7.26.0(@babel/core@7.26.0)': + '@babel/plugin-transform-class-static-block@7.26.0(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/core': 7.26.7 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.7) '@babel/helper-plugin-utils': 7.26.5 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-classes@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-classes@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-annotate-as-pure': 7.25.9 '@babel/helper-compilation-targets': 7.26.5 '@babel/helper-plugin-utils': 7.26.5 - '@babel/helper-replace-supers': 7.26.5(@babel/core@7.26.0) - '@babel/traverse': 7.26.5 + '@babel/helper-replace-supers': 7.26.5(@babel/core@7.26.7) + '@babel/traverse': 7.26.7 globals: 11.12.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-computed-properties@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-computed-properties@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 '@babel/template': 7.25.9 - '@babel/plugin-transform-destructuring@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-destructuring@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-dotall-regex@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-dotall-regex@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.0) + '@babel/core': 7.26.7 + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.7) '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-duplicate-keys@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-duplicate-keys@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.0) + '@babel/core': 7.26.7 + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.7) '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-dynamic-import@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-dynamic-import@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-exponentiation-operator@7.26.3(@babel/core@7.26.0)': + '@babel/plugin-transform-exponentiation-operator@7.26.3(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-export-namespace-from@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-export-namespace-from@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-for-of@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-for-of@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-function-name@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-function-name@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-compilation-targets': 7.26.5 '@babel/helper-plugin-utils': 7.26.5 - '@babel/traverse': 7.26.5 + '@babel/traverse': 7.26.7 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-json-strings@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-json-strings@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-literals@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-literals@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-logical-assignment-operators@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-logical-assignment-operators@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-member-expression-literals@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-member-expression-literals@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-modules-amd@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-modules-amd@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/core': 7.26.7 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.7) '@babel/helper-plugin-utils': 7.26.5 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-commonjs@7.26.3(@babel/core@7.26.0)': + '@babel/plugin-transform-modules-commonjs@7.26.3(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/core': 7.26.7 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.7) '@babel/helper-plugin-utils': 7.26.5 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-systemjs@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-modules-systemjs@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/core': 7.26.7 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.7) '@babel/helper-plugin-utils': 7.26.5 '@babel/helper-validator-identifier': 7.25.9 - '@babel/traverse': 7.26.5 + '@babel/traverse': 7.26.7 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-umd@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-modules-umd@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/core': 7.26.7 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.7) '@babel/helper-plugin-utils': 7.26.5 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-named-capturing-groups-regex@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-named-capturing-groups-regex@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.0) + '@babel/core': 7.26.7 + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.7) '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-new-target@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-new-target@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-nullish-coalescing-operator@7.26.6(@babel/core@7.26.0)': + '@babel/plugin-transform-nullish-coalescing-operator@7.26.6(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-numeric-separator@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-numeric-separator@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-object-rest-spread@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-object-rest-spread@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-compilation-targets': 7.26.5 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.26.7) - '@babel/plugin-transform-object-super@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-object-super@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/helper-replace-supers': 7.26.5(@babel/core@7.26.0) + '@babel/helper-replace-supers': 7.26.5(@babel/core@7.26.7) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-optional-catch-binding@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-optional-catch-binding@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-optional-chaining@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-optional-chaining@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-parameters@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-parameters@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-private-methods@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-private-methods@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/core': 7.26.7 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.7) '@babel/helper-plugin-utils': 7.26.5 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-private-property-in-object@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-private-property-in-object@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-annotate-as-pure': 7.25.9 - '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.7) '@babel/helper-plugin-utils': 7.26.5 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-property-literals@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-property-literals@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-react-constant-elements@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-react-constant-elements@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-react-display-name@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-react-display-name@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-react-jsx-development@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-react-jsx-development@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/plugin-transform-react-jsx': 7.25.9(@babel/core@7.26.0) + '@babel/core': 7.26.7 + '@babel/plugin-transform-react-jsx': 7.25.9(@babel/core@7.26.7) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-annotate-as-pure': 7.25.9 '@babel/helper-module-imports': 7.25.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0) - '@babel/types': 7.26.5 + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.7) + '@babel/types': 7.26.7 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-pure-annotations@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-react-pure-annotations@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-annotate-as-pure': 7.25.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-regenerator@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-regenerator@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 regenerator-transform: 0.15.2 - '@babel/plugin-transform-regexp-modifiers@7.26.0(@babel/core@7.26.0)': + '@babel/plugin-transform-regexp-modifiers@7.26.0(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.0) + '@babel/core': 7.26.7 + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.7) '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-reserved-words@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-reserved-words@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-shorthand-properties@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-shorthand-properties@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-spread@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-spread@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-sticky-regex@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-sticky-regex@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-template-literals@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-template-literals@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-typeof-symbol@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-typeof-symbol@7.26.7(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-typescript@7.26.5(@babel/core@7.26.0)': + '@babel/plugin-transform-typescript@7.26.7(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-annotate-as-pure': 7.25.9 - '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.7) '@babel/helper-plugin-utils': 7.26.5 '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 - '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.7) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-unicode-escapes@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-unicode-escapes@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-unicode-property-regex@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-unicode-property-regex@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.0) + '@babel/core': 7.26.7 + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.7) '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-unicode-regex@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-unicode-regex@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.0) + '@babel/core': 7.26.7 + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.7) '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-unicode-sets-regex@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-unicode-sets-regex@7.25.9(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.0) + '@babel/core': 7.26.7 + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.7) '@babel/helper-plugin-utils': 7.26.5 - '@babel/preset-env@7.26.0(@babel/core@7.26.0)': + '@babel/preset-env@7.26.7(@babel/core@7.26.7)': dependencies: '@babel/compat-data': 7.26.5 - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-compilation-targets': 7.26.5 '@babel/helper-plugin-utils': 7.26.5 '@babel/helper-validator-option': 7.25.9 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.26.0) - '@babel/plugin-syntax-import-assertions': 7.26.0(@babel/core@7.26.0) - '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.0) - '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.26.0) - '@babel/plugin-transform-arrow-functions': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-async-generator-functions': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-async-to-generator': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-block-scoped-functions': 7.26.5(@babel/core@7.26.0) - '@babel/plugin-transform-block-scoping': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-class-properties': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-class-static-block': 7.26.0(@babel/core@7.26.0) - '@babel/plugin-transform-classes': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-computed-properties': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-destructuring': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-dotall-regex': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-duplicate-keys': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-dynamic-import': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-exponentiation-operator': 7.26.3(@babel/core@7.26.0) - '@babel/plugin-transform-export-namespace-from': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-for-of': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-function-name': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-json-strings': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-literals': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-logical-assignment-operators': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-member-expression-literals': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-modules-amd': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-modules-commonjs': 7.26.3(@babel/core@7.26.0) - '@babel/plugin-transform-modules-systemjs': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-modules-umd': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-named-capturing-groups-regex': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-new-target': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-nullish-coalescing-operator': 7.26.6(@babel/core@7.26.0) - '@babel/plugin-transform-numeric-separator': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-object-rest-spread': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-object-super': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-optional-catch-binding': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-private-property-in-object': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-property-literals': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-regenerator': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-regexp-modifiers': 7.26.0(@babel/core@7.26.0) - '@babel/plugin-transform-reserved-words': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-shorthand-properties': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-spread': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-sticky-regex': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-template-literals': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-typeof-symbol': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-unicode-escapes': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-unicode-property-regex': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-unicode-regex': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-unicode-sets-regex': 7.25.9(@babel/core@7.26.0) - '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.26.0) - babel-plugin-polyfill-corejs2: 0.4.12(@babel/core@7.26.0) - babel-plugin-polyfill-corejs3: 0.10.6(@babel/core@7.26.0) - babel-plugin-polyfill-regenerator: 0.6.3(@babel/core@7.26.0) + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.26.7) + '@babel/plugin-syntax-import-assertions': 7.26.0(@babel/core@7.26.7) + '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.7) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.26.7) + '@babel/plugin-transform-arrow-functions': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-async-generator-functions': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-async-to-generator': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-block-scoped-functions': 7.26.5(@babel/core@7.26.7) + '@babel/plugin-transform-block-scoping': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-class-properties': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-class-static-block': 7.26.0(@babel/core@7.26.7) + '@babel/plugin-transform-classes': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-computed-properties': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-destructuring': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-dotall-regex': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-duplicate-keys': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-dynamic-import': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-exponentiation-operator': 7.26.3(@babel/core@7.26.7) + '@babel/plugin-transform-export-namespace-from': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-for-of': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-function-name': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-json-strings': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-literals': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-logical-assignment-operators': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-member-expression-literals': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-modules-amd': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-modules-commonjs': 7.26.3(@babel/core@7.26.7) + '@babel/plugin-transform-modules-systemjs': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-modules-umd': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-named-capturing-groups-regex': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-new-target': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-nullish-coalescing-operator': 7.26.6(@babel/core@7.26.7) + '@babel/plugin-transform-numeric-separator': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-object-rest-spread': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-object-super': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-optional-catch-binding': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-private-property-in-object': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-property-literals': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-regenerator': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-regexp-modifiers': 7.26.0(@babel/core@7.26.7) + '@babel/plugin-transform-reserved-words': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-shorthand-properties': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-spread': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-sticky-regex': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-template-literals': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-typeof-symbol': 7.26.7(@babel/core@7.26.7) + '@babel/plugin-transform-unicode-escapes': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-unicode-property-regex': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-unicode-regex': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-unicode-sets-regex': 7.25.9(@babel/core@7.26.7) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.26.7) + babel-plugin-polyfill-corejs2: 0.4.12(@babel/core@7.26.7) + babel-plugin-polyfill-corejs3: 0.10.6(@babel/core@7.26.7) + babel-plugin-polyfill-regenerator: 0.6.3(@babel/core@7.26.7) core-js-compat: 3.40.0 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.26.0)': + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/types': 7.26.5 + '@babel/types': 7.26.7 esutils: 2.0.3 - '@babel/preset-react@7.26.3(@babel/core@7.26.0)': + '@babel/preset-react@7.26.3(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 '@babel/helper-validator-option': 7.25.9 - '@babel/plugin-transform-react-display-name': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-react-jsx': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-react-jsx-development': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-react-pure-annotations': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-react-display-name': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-react-jsx': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-react-jsx-development': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-react-pure-annotations': 7.25.9(@babel/core@7.26.7) transitivePeerDependencies: - supports-color - '@babel/preset-typescript@7.26.0(@babel/core@7.26.0)': + '@babel/preset-typescript@7.26.0(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/helper-plugin-utils': 7.26.5 '@babel/helper-validator-option': 7.25.9 - '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-modules-commonjs': 7.26.3(@babel/core@7.26.0) - '@babel/plugin-transform-typescript': 7.26.5(@babel/core@7.26.0) + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-transform-modules-commonjs': 7.26.3(@babel/core@7.26.7) + '@babel/plugin-transform-typescript': 7.26.7(@babel/core@7.26.7) transitivePeerDependencies: - supports-color - '@babel/runtime@7.26.0': + '@babel/runtime@7.26.7': dependencies: regenerator-runtime: 0.14.1 '@babel/template@7.25.9': dependencies: '@babel/code-frame': 7.26.2 - '@babel/parser': 7.26.5 - '@babel/types': 7.26.5 + '@babel/parser': 7.26.7 + '@babel/types': 7.26.7 '@babel/traverse@7.23.2': dependencies: @@ -11244,20 +11762,20 @@ snapshots: '@babel/helper-function-name': 7.24.7 '@babel/helper-hoist-variables': 7.24.7 '@babel/helper-split-export-declaration': 7.24.7 - '@babel/parser': 7.26.5 - '@babel/types': 7.26.5 + '@babel/parser': 7.26.7 + '@babel/types': 7.26.7 debug: 4.4.0 globals: 11.12.0 transitivePeerDependencies: - supports-color - '@babel/traverse@7.26.5': + '@babel/traverse@7.26.7': dependencies: '@babel/code-frame': 7.26.2 '@babel/generator': 7.26.5 - '@babel/parser': 7.26.5 + '@babel/parser': 7.26.7 '@babel/template': 7.25.9 - '@babel/types': 7.26.5 + '@babel/types': 7.26.7 debug: 4.4.0 globals: 11.12.0 transitivePeerDependencies: @@ -11268,7 +11786,7 @@ snapshots: '@babel/helper-validator-identifier': 7.25.9 to-fast-properties: 2.0.0 - '@babel/types@7.26.5': + '@babel/types@7.26.7': dependencies: '@babel/helper-string-parser': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 @@ -11288,6 +11806,9 @@ snapshots: '@types/tough-cookie': 4.0.5 tough-cookie: 4.1.4 + '@colors/colors@1.5.0': + optional: true + '@csstools/color-helpers@5.0.1': {} '@csstools/css-calc@2.1.1(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': @@ -11308,6 +11829,8 @@ snapshots: '@csstools/css-tokenizer@3.0.3': {} + '@discoveryjs/json-ext@0.5.7': {} + '@emnapi/core@1.3.1': dependencies: '@emnapi/wasi-threads': 1.0.1 @@ -11324,7 +11847,7 @@ snapshots: '@emotion/babel-plugin@11.13.5': dependencies: '@babel/helper-module-imports': 7.25.9 - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.26.7 '@emotion/hash': 0.9.2 '@emotion/memoize': 0.9.0 '@emotion/serialize': 1.3.3 @@ -11353,19 +11876,19 @@ snapshots: '@emotion/memoize@0.9.0': {} - '@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1)': + '@emotion/react@11.14.0(@types/react@19.0.8)(react@19.0.0)': dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.26.7 '@emotion/babel-plugin': 11.13.5 '@emotion/cache': 11.14.0 '@emotion/serialize': 1.3.3 - '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@18.3.1) + '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.0.0) '@emotion/utils': 1.4.2 '@emotion/weak-memoize': 0.4.0 hoist-non-react-statics: 3.3.2 - react: 18.3.1 + react: 19.0.0 optionalDependencies: - '@types/react': 18.3.18 + '@types/react': 19.0.8 transitivePeerDependencies: - supports-color @@ -11379,26 +11902,26 @@ snapshots: '@emotion/sheet@1.4.0': {} - '@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1)': + '@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.8)(react@19.0.0))(@types/react@19.0.8)(react@19.0.0)': dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.26.7 '@emotion/babel-plugin': 11.13.5 '@emotion/is-prop-valid': 1.3.1 - '@emotion/react': 11.14.0(@types/react@18.3.18)(react@18.3.1) + '@emotion/react': 11.14.0(@types/react@19.0.8)(react@19.0.0) '@emotion/serialize': 1.3.3 - '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@18.3.1) + '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.0.0) '@emotion/utils': 1.4.2 - react: 18.3.1 + react: 19.0.0 optionalDependencies: - '@types/react': 18.3.18 + '@types/react': 19.0.8 transitivePeerDependencies: - supports-color '@emotion/unitless@0.10.0': {} - '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@18.3.1)': + '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@19.0.0)': dependencies: - react: 18.3.1 + react: 19.0.0 '@emotion/utils@1.4.2': {} @@ -11637,7 +12160,7 @@ snapshots: '@eslint/js@8.57.1': {} - '@faker-js/faker@9.3.0': {} + '@faker-js/faker@9.4.0': {} '@floating-ui/core@1.6.9': dependencies: @@ -11648,17 +12171,43 @@ snapshots: '@floating-ui/core': 1.6.9 '@floating-ui/utils': 0.2.9 - '@floating-ui/react-dom@2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@floating-ui/react-dom@2.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@floating-ui/dom': 1.6.13 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) '@floating-ui/utils@0.2.9': {} - '@hookform/resolvers@3.10.0(react-hook-form@7.54.2(react@18.3.1))': + '@formatjs/ecma402-abstract@2.3.2': + dependencies: + '@formatjs/fast-memoize': 2.2.6 + '@formatjs/intl-localematcher': 0.5.10 + decimal.js: 10.5.0 + tslib: 2.8.1 + + '@formatjs/fast-memoize@2.2.6': + dependencies: + tslib: 2.8.1 + + '@formatjs/icu-messageformat-parser@2.11.0': + dependencies: + '@formatjs/ecma402-abstract': 2.3.2 + '@formatjs/icu-skeleton-parser': 1.8.12 + tslib: 2.8.1 + + '@formatjs/icu-skeleton-parser@1.8.12': + dependencies: + '@formatjs/ecma402-abstract': 2.3.2 + tslib: 2.8.1 + + '@formatjs/intl-localematcher@0.5.10': + dependencies: + tslib: 2.8.1 + + '@hookform/resolvers@3.10.0(react-hook-form@7.54.2(react@19.0.0))': dependencies: - react-hook-form: 7.54.2(react@18.3.1) + react-hook-form: 7.54.2(react@19.0.0) '@humanwhocodes/config-array@0.13.0': dependencies: @@ -11927,7 +12476,7 @@ snapshots: '@jest/transform@29.7.0': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.25 babel-plugin-istanbul: 6.1.1 @@ -11964,12 +12513,6 @@ snapshots: '@jridgewell/set-array@1.2.1': {} - '@jridgewell/source-map@0.3.6': - dependencies: - '@jridgewell/gen-mapping': 0.3.8 - '@jridgewell/trace-mapping': 0.3.25 - optional: true - '@jridgewell/sourcemap-codec@1.5.0': {} '@jridgewell/trace-mapping@0.3.25': @@ -11993,6 +12536,14 @@ snapshots: '@lexical/utils': 0.23.1 lexical: 0.23.1 + '@lexical/clipboard@0.27.1': + dependencies: + '@lexical/html': 0.27.1 + '@lexical/list': 0.27.1 + '@lexical/selection': 0.27.1 + '@lexical/utils': 0.27.1 + lexical: 0.27.1 + '@lexical/code@0.21.0': dependencies: '@lexical/utils': 0.21.0 @@ -12005,7 +12556,7 @@ snapshots: lexical: 0.23.1 prismjs: 1.29.0 - '@lexical/devtools-core@0.21.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@lexical/devtools-core@0.21.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@lexical/html': 0.21.0 '@lexical/link': 0.21.0 @@ -12013,10 +12564,10 @@ snapshots: '@lexical/table': 0.21.0 '@lexical/utils': 0.21.0 lexical: 0.21.0 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) - '@lexical/devtools-core@0.23.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@lexical/devtools-core@0.23.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@lexical/html': 0.23.1 '@lexical/link': 0.23.1 @@ -12024,8 +12575,8 @@ snapshots: '@lexical/table': 0.23.1 '@lexical/utils': 0.23.1 lexical: 0.23.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) '@lexical/dragon@0.21.0': dependencies: @@ -12067,6 +12618,12 @@ snapshots: '@lexical/utils': 0.23.1 lexical: 0.23.1 + '@lexical/html@0.27.1': + dependencies: + '@lexical/selection': 0.27.1 + '@lexical/utils': 0.27.1 + lexical: 0.27.1 + '@lexical/link@0.21.0': dependencies: '@lexical/utils': 0.21.0 @@ -12077,6 +12634,11 @@ snapshots: '@lexical/utils': 0.23.1 lexical: 0.23.1 + '@lexical/link@0.27.1': + dependencies: + '@lexical/utils': 0.27.1 + lexical: 0.27.1 + '@lexical/list@0.21.0': dependencies: '@lexical/utils': 0.21.0 @@ -12087,6 +12649,11 @@ snapshots: '@lexical/utils': 0.23.1 lexical: 0.23.1 + '@lexical/list@0.27.1': + dependencies: + '@lexical/utils': 0.27.1 + lexical: 0.27.1 + '@lexical/mark@0.21.0': dependencies: '@lexical/utils': 0.21.0 @@ -12147,11 +12714,11 @@ snapshots: '@lexical/utils': 0.23.1 lexical: 0.23.1 - '@lexical/react@0.21.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(yjs@13.6.22)': + '@lexical/react@0.21.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(yjs@13.6.23)': dependencies: '@lexical/clipboard': 0.21.0 '@lexical/code': 0.21.0 - '@lexical/devtools-core': 0.21.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@lexical/devtools-core': 0.21.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@lexical/dragon': 0.21.0 '@lexical/hashtag': 0.21.0 '@lexical/history': 0.21.0 @@ -12166,19 +12733,19 @@ snapshots: '@lexical/table': 0.21.0 '@lexical/text': 0.21.0 '@lexical/utils': 0.21.0 - '@lexical/yjs': 0.21.0(yjs@13.6.22) + '@lexical/yjs': 0.21.0(yjs@13.6.23) lexical: 0.21.0 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-error-boundary: 3.1.4(react@18.3.1) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react-error-boundary: 3.1.4(react@19.0.0) transitivePeerDependencies: - yjs - '@lexical/react@0.23.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(yjs@13.6.22)': + '@lexical/react@0.23.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(yjs@13.6.23)': dependencies: '@lexical/clipboard': 0.23.1 '@lexical/code': 0.23.1 - '@lexical/devtools-core': 0.23.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@lexical/devtools-core': 0.23.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@lexical/dragon': 0.23.1 '@lexical/hashtag': 0.23.1 '@lexical/history': 0.23.1 @@ -12193,11 +12760,11 @@ snapshots: '@lexical/table': 0.23.1 '@lexical/text': 0.23.1 '@lexical/utils': 0.23.1 - '@lexical/yjs': 0.23.1(yjs@13.6.22) + '@lexical/yjs': 0.23.1(yjs@13.6.23) lexical: 0.23.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-error-boundary: 3.1.4(react@18.3.1) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react-error-boundary: 3.1.4(react@19.0.0) transitivePeerDependencies: - yjs @@ -12223,6 +12790,10 @@ snapshots: dependencies: lexical: 0.23.1 + '@lexical/selection@0.27.1': + dependencies: + lexical: 0.27.1 + '@lexical/table@0.21.0': dependencies: '@lexical/clipboard': 0.21.0 @@ -12235,6 +12806,12 @@ snapshots: '@lexical/utils': 0.23.1 lexical: 0.23.1 + '@lexical/table@0.27.1': + dependencies: + '@lexical/clipboard': 0.27.1 + '@lexical/utils': 0.27.1 + lexical: 0.27.1 + '@lexical/text@0.21.0': dependencies: lexical: 0.21.0 @@ -12257,39 +12834,37 @@ snapshots: '@lexical/table': 0.23.1 lexical: 0.23.1 - '@lexical/yjs@0.21.0(yjs@13.6.22)': + '@lexical/utils@0.27.1': + dependencies: + '@lexical/list': 0.27.1 + '@lexical/selection': 0.27.1 + '@lexical/table': 0.27.1 + lexical: 0.27.1 + + '@lexical/yjs@0.21.0(yjs@13.6.23)': dependencies: '@lexical/offset': 0.21.0 '@lexical/selection': 0.21.0 lexical: 0.21.0 - yjs: 13.6.22 + yjs: 13.6.23 - '@lexical/yjs@0.23.1(yjs@13.6.22)': + '@lexical/yjs@0.23.1(yjs@13.6.23)': dependencies: '@lexical/offset': 0.23.1 '@lexical/selection': 0.23.1 lexical: 0.23.1 - yjs: 13.6.22 - - '@lottiefiles/react-lottie-player@3.5.4(react@18.3.1)': - dependencies: - lottie-web: 5.12.2 - react: 18.3.1 + yjs: 13.6.23 - '@lottiefiles/react-lottie-player@3.6.0(react@18.3.1)': + '@lottiefiles/react-lottie-player@3.6.0(react@19.0.0)': dependencies: lottie-web: 5.12.2 - react: 18.3.1 + react: 19.0.0 - '@mdn/browser-compat-data@5.6.29': {} + '@mdn/browser-compat-data@5.6.33': {} - '@mswjs/http-middleware@0.10.3(msw@2.7.0(@types/node@20.13.0)(typescript@5.4.5))': + '@minimistjs/subarg@1.0.0': dependencies: - express: 4.21.2 - msw: 2.7.0(@types/node@20.13.0)(typescript@5.4.5) - strict-event-emitter: 0.5.1 - transitivePeerDependencies: - - supports-color + minimist: 1.2.8 '@mswjs/interceptors@0.37.5': dependencies: @@ -12306,30 +12881,37 @@ snapshots: '@emnapi/runtime': 1.3.1 '@tybys/wasm-util': 0.9.0 - '@next/env@15.1.4': {} + '@next/bundle-analyzer@15.1.6': + dependencies: + webpack-bundle-analyzer: 4.10.1 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@next/env@15.1.6': {} - '@next/swc-darwin-arm64@15.1.4': + '@next/swc-darwin-arm64@15.1.6': optional: true - '@next/swc-darwin-x64@15.1.4': + '@next/swc-darwin-x64@15.1.6': optional: true - '@next/swc-linux-arm64-gnu@15.1.4': + '@next/swc-linux-arm64-gnu@15.1.6': optional: true - '@next/swc-linux-arm64-musl@15.1.4': + '@next/swc-linux-arm64-musl@15.1.6': optional: true - '@next/swc-linux-x64-gnu@15.1.4': + '@next/swc-linux-x64-gnu@15.1.6': optional: true - '@next/swc-linux-x64-musl@15.1.4': + '@next/swc-linux-x64-musl@15.1.6': optional: true - '@next/swc-win32-arm64-msvc@15.1.4': + '@next/swc-win32-arm64-msvc@15.1.6': optional: true - '@next/swc-win32-x64-msvc@15.1.4': + '@next/swc-win32-x64-msvc@15.1.6': optional: true '@nodelib/fs.scandir@2.1.5': @@ -12344,34 +12926,34 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.18.0 - '@nx/nx-darwin-arm64@20.3.1': + '@nx/nx-darwin-arm64@20.3.3': optional: true - '@nx/nx-darwin-x64@20.3.1': + '@nx/nx-darwin-x64@20.3.3': optional: true - '@nx/nx-freebsd-x64@20.3.1': + '@nx/nx-freebsd-x64@20.3.3': optional: true - '@nx/nx-linux-arm-gnueabihf@20.3.1': + '@nx/nx-linux-arm-gnueabihf@20.3.3': optional: true - '@nx/nx-linux-arm64-gnu@20.3.1': + '@nx/nx-linux-arm64-gnu@20.3.3': optional: true - '@nx/nx-linux-arm64-musl@20.3.1': + '@nx/nx-linux-arm64-musl@20.3.3': optional: true - '@nx/nx-linux-x64-gnu@20.3.1': + '@nx/nx-linux-x64-gnu@20.3.3': optional: true - '@nx/nx-linux-x64-musl@20.3.1': + '@nx/nx-linux-x64-musl@20.3.3': optional: true - '@nx/nx-win32-arm64-msvc@20.3.1': + '@nx/nx-win32-arm64-msvc@20.3.3': optional: true - '@nx/nx-win32-x64-msvc@20.3.1': + '@nx/nx-win32-x64-msvc@20.3.3': optional: true '@open-draft/deferred-promise@2.2.0': {} @@ -12383,336 +12965,404 @@ snapshots: '@open-draft/until@2.1.0': {} + '@paulirish/trace_engine@0.0.39': + dependencies: + third-party-web: 0.26.6 + '@pkgjs/parseargs@0.11.0': optional: true '@pkgr/core@0.1.1': {} - '@playwright/test@1.49.1': + '@playwright/test@1.50.0': dependencies: - playwright: 1.49.1 + playwright: 1.50.0 '@polka/url@1.0.0-next.28': {} + '@puppeteer/browsers@2.6.1': + dependencies: + debug: 4.4.0 + extract-zip: 2.0.1 + progress: 2.0.3 + proxy-agent: 6.5.0 + semver: 7.6.3 + tar-fs: 3.0.8 + unbzip2-stream: 1.4.3 + yargs: 17.7.2 + transitivePeerDependencies: + - bare-buffer + - supports-color + + '@puppeteer/browsers@2.7.0': + dependencies: + debug: 4.4.0 + extract-zip: 2.0.1 + progress: 2.0.3 + proxy-agent: 6.5.0 + semver: 7.6.3 + tar-fs: 3.0.8 + unbzip2-stream: 1.4.3 + yargs: 17.7.2 + transitivePeerDependencies: + - bare-buffer + - supports-color + '@radix-ui/primitive@1.1.1': {} - '@radix-ui/react-arrow@1.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-arrow@1.1.1(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) optionalDependencies: - '@types/react': 18.3.18 - '@types/react-dom': 18.3.5(@types/react@18.3.18) + '@types/react': 19.0.8 + '@types/react-dom': 19.0.3(@types/react@19.0.8) - '@radix-ui/react-collection@1.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-collection@1.1.1(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': 1.1.1(@types/react@18.3.18)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.8)(react@19.0.0) + '@radix-ui/react-context': 1.1.1(@types/react@19.0.8)(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-slot': 1.1.1(@types/react@19.0.8)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) optionalDependencies: - '@types/react': 18.3.18 - '@types/react-dom': 18.3.5(@types/react@18.3.18) + '@types/react': 19.0.8 + '@types/react-dom': 19.0.3(@types/react@19.0.8) - '@radix-ui/react-compose-refs@1.1.1(@types/react@18.3.18)(react@18.3.1)': + '@radix-ui/react-compose-refs@1.1.1(@types/react@19.0.8)(react@19.0.0)': dependencies: - react: 18.3.1 + react: 19.0.0 optionalDependencies: - '@types/react': 18.3.18 + '@types/react': 19.0.8 - '@radix-ui/react-context@1.1.1(@types/react@18.3.18)(react@18.3.1)': + '@radix-ui/react-context@1.1.1(@types/react@19.0.8)(react@19.0.0)': dependencies: - react: 18.3.1 + react: 19.0.0 optionalDependencies: - '@types/react': 18.3.18 + '@types/react': 19.0.8 - '@radix-ui/react-dialog@1.1.4(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-dialog@1.1.5(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-focus-scope': 1.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-id': 1.1.0(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-portal': 1.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': 1.1.1(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.18)(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.8)(react@19.0.0) + '@radix-ui/react-context': 1.1.1(@types/react@19.0.8)(react@19.0.0) + '@radix-ui/react-dismissable-layer': 1.1.4(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-focus-guards': 1.1.1(@types/react@19.0.8)(react@19.0.0) + '@radix-ui/react-focus-scope': 1.1.1(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-id': 1.1.0(@types/react@19.0.8)(react@19.0.0) + '@radix-ui/react-portal': 1.1.3(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-presence': 1.1.2(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-slot': 1.1.1(@types/react@19.0.8)(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.0.8)(react@19.0.0) aria-hidden: 1.2.4 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.6.2(@types/react@18.3.18)(react@18.3.1) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react-remove-scroll: 2.6.3(@types/react@19.0.8)(react@19.0.0) optionalDependencies: - '@types/react': 18.3.18 - '@types/react-dom': 18.3.5(@types/react@18.3.18) + '@types/react': 19.0.8 + '@types/react-dom': 19.0.3(@types/react@19.0.8) - '@radix-ui/react-direction@1.1.0(@types/react@18.3.18)(react@18.3.1)': + '@radix-ui/react-direction@1.1.0(@types/react@19.0.8)(react@19.0.0)': dependencies: - react: 18.3.1 + react: 19.0.0 optionalDependencies: - '@types/react': 18.3.18 + '@types/react': 19.0.8 - '@radix-ui/react-dismissable-layer@1.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-dismissable-layer@1.1.4(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@18.3.18)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.8)(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.8)(react@19.0.0) + '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@19.0.8)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) optionalDependencies: - '@types/react': 18.3.18 - '@types/react-dom': 18.3.5(@types/react@18.3.18) + '@types/react': 19.0.8 + '@types/react-dom': 19.0.3(@types/react@19.0.8) - '@radix-ui/react-dropdown-menu@2.1.4(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-dropdown-menu@2.1.5(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-id': 1.1.0(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-menu': 2.1.4(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.18)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.8)(react@19.0.0) + '@radix-ui/react-context': 1.1.1(@types/react@19.0.8)(react@19.0.0) + '@radix-ui/react-id': 1.1.0(@types/react@19.0.8)(react@19.0.0) + '@radix-ui/react-menu': 2.1.5(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.0.8)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) optionalDependencies: - '@types/react': 18.3.18 - '@types/react-dom': 18.3.5(@types/react@18.3.18) + '@types/react': 19.0.8 + '@types/react-dom': 19.0.3(@types/react@19.0.8) - '@radix-ui/react-focus-guards@1.1.1(@types/react@18.3.18)(react@18.3.1)': + '@radix-ui/react-focus-guards@1.1.1(@types/react@19.0.8)(react@19.0.0)': dependencies: - react: 18.3.1 + react: 19.0.0 optionalDependencies: - '@types/react': 18.3.18 + '@types/react': 19.0.8 - '@radix-ui/react-focus-scope@1.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-focus-scope@1.1.1(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.18)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.8)(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.8)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) optionalDependencies: - '@types/react': 18.3.18 - '@types/react-dom': 18.3.5(@types/react@18.3.18) + '@types/react': 19.0.8 + '@types/react-dom': 19.0.3(@types/react@19.0.8) + + '@radix-ui/react-icons@1.3.2(react@19.0.0)': + dependencies: + react: 19.0.0 - '@radix-ui/react-id@1.1.0(@types/react@18.3.18)(react@18.3.1)': + '@radix-ui/react-id@1.1.0(@types/react@19.0.8)(react@19.0.0)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.18)(react@18.3.1) - react: 18.3.1 + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.8)(react@19.0.0) + react: 19.0.0 optionalDependencies: - '@types/react': 18.3.18 + '@types/react': 19.0.8 - '@radix-ui/react-menu@2.1.4(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-menu@2.1.5(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-collection': 1.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-direction': 1.1.0(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-focus-scope': 1.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-id': 1.1.0(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-popper': 1.2.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-portal': 1.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-roving-focus': 1.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': 1.1.1(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.18)(react@18.3.1) + '@radix-ui/react-collection': 1.1.1(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.8)(react@19.0.0) + '@radix-ui/react-context': 1.1.1(@types/react@19.0.8)(react@19.0.0) + '@radix-ui/react-direction': 1.1.0(@types/react@19.0.8)(react@19.0.0) + '@radix-ui/react-dismissable-layer': 1.1.4(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-focus-guards': 1.1.1(@types/react@19.0.8)(react@19.0.0) + '@radix-ui/react-focus-scope': 1.1.1(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-id': 1.1.0(@types/react@19.0.8)(react@19.0.0) + '@radix-ui/react-popper': 1.2.1(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-portal': 1.1.3(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-presence': 1.1.2(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-roving-focus': 1.1.1(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-slot': 1.1.1(@types/react@19.0.8)(react@19.0.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.8)(react@19.0.0) aria-hidden: 1.2.4 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.6.2(@types/react@18.3.18)(react@18.3.1) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react-remove-scroll: 2.6.3(@types/react@19.0.8)(react@19.0.0) optionalDependencies: - '@types/react': 18.3.18 - '@types/react-dom': 18.3.5(@types/react@18.3.18) - - '@radix-ui/react-popper@1.2.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@floating-ui/react-dom': 2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-arrow': 1.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-use-rect': 1.1.0(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.18)(react@18.3.1) + '@types/react': 19.0.8 + '@types/react-dom': 19.0.3(@types/react@19.0.8) + + '@radix-ui/react-popper@1.2.1(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@floating-ui/react-dom': 2.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-arrow': 1.1.1(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.8)(react@19.0.0) + '@radix-ui/react-context': 1.1.1(@types/react@19.0.8)(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.8)(react@19.0.0) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.8)(react@19.0.0) + '@radix-ui/react-use-rect': 1.1.0(@types/react@19.0.8)(react@19.0.0) + '@radix-ui/react-use-size': 1.1.0(@types/react@19.0.8)(react@19.0.0) '@radix-ui/rect': 1.1.0 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) optionalDependencies: - '@types/react': 18.3.18 - '@types/react-dom': 18.3.5(@types/react@18.3.18) + '@types/react': 19.0.8 + '@types/react-dom': 19.0.3(@types/react@19.0.8) - '@radix-ui/react-portal@1.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-portal@1.1.3(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.18)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.8)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) optionalDependencies: - '@types/react': 18.3.18 - '@types/react-dom': 18.3.5(@types/react@18.3.18) + '@types/react': 19.0.8 + '@types/react-dom': 19.0.3(@types/react@19.0.8) - '@radix-ui/react-presence@1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-presence@1.1.2(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.18)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.8)(react@19.0.0) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.8)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) optionalDependencies: - '@types/react': 18.3.18 - '@types/react-dom': 18.3.5(@types/react@18.3.18) + '@types/react': 19.0.8 + '@types/react-dom': 19.0.3(@types/react@19.0.8) - '@radix-ui/react-primitive@2.0.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-primitive@2.0.1(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@radix-ui/react-slot': 1.1.1(@types/react@18.3.18)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@radix-ui/react-slot': 1.1.1(@types/react@19.0.8)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) optionalDependencies: - '@types/react': 18.3.18 - '@types/react-dom': 18.3.5(@types/react@18.3.18) + '@types/react': 19.0.8 + '@types/react-dom': 19.0.3(@types/react@19.0.8) - '@radix-ui/react-roving-focus@1.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-roving-focus@1.1.1(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-collection': 1.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-direction': 1.1.0(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-id': 1.1.0(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.18)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@radix-ui/react-collection': 1.1.1(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.8)(react@19.0.0) + '@radix-ui/react-context': 1.1.1(@types/react@19.0.8)(react@19.0.0) + '@radix-ui/react-direction': 1.1.0(@types/react@19.0.8)(react@19.0.0) + '@radix-ui/react-id': 1.1.0(@types/react@19.0.8)(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.8)(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.0.8)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) optionalDependencies: - '@types/react': 18.3.18 - '@types/react-dom': 18.3.5(@types/react@18.3.18) + '@types/react': 19.0.8 + '@types/react-dom': 19.0.3(@types/react@19.0.8) - '@radix-ui/react-slot@1.1.1(@types/react@18.3.18)(react@18.3.1)': + '@radix-ui/react-slot@1.1.1(@types/react@19.0.8)(react@19.0.0)': dependencies: - '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1) - react: 18.3.1 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.8)(react@19.0.0) + react: 19.0.0 optionalDependencies: - '@types/react': 18.3.18 + '@types/react': 19.0.8 - '@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.18)(react@18.3.1)': + '@radix-ui/react-use-callback-ref@1.1.0(@types/react@19.0.8)(react@19.0.0)': dependencies: - react: 18.3.1 + react: 19.0.0 optionalDependencies: - '@types/react': 18.3.18 + '@types/react': 19.0.8 - '@radix-ui/react-use-controllable-state@1.1.0(@types/react@18.3.18)(react@18.3.1)': + '@radix-ui/react-use-controllable-state@1.1.0(@types/react@19.0.8)(react@19.0.0)': dependencies: - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.18)(react@18.3.1) - react: 18.3.1 + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.8)(react@19.0.0) + react: 19.0.0 optionalDependencies: - '@types/react': 18.3.18 + '@types/react': 19.0.8 - '@radix-ui/react-use-escape-keydown@1.1.0(@types/react@18.3.18)(react@18.3.1)': + '@radix-ui/react-use-escape-keydown@1.1.0(@types/react@19.0.8)(react@19.0.0)': dependencies: - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.18)(react@18.3.1) - react: 18.3.1 + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.8)(react@19.0.0) + react: 19.0.0 optionalDependencies: - '@types/react': 18.3.18 + '@types/react': 19.0.8 - '@radix-ui/react-use-layout-effect@1.1.0(@types/react@18.3.18)(react@18.3.1)': + '@radix-ui/react-use-layout-effect@1.1.0(@types/react@19.0.8)(react@19.0.0)': dependencies: - react: 18.3.1 + react: 19.0.0 optionalDependencies: - '@types/react': 18.3.18 + '@types/react': 19.0.8 - '@radix-ui/react-use-rect@1.1.0(@types/react@18.3.18)(react@18.3.1)': + '@radix-ui/react-use-rect@1.1.0(@types/react@19.0.8)(react@19.0.0)': dependencies: '@radix-ui/rect': 1.1.0 - react: 18.3.1 + react: 19.0.0 optionalDependencies: - '@types/react': 18.3.18 + '@types/react': 19.0.8 - '@radix-ui/react-use-size@1.1.0(@types/react@18.3.18)(react@18.3.1)': + '@radix-ui/react-use-size@1.1.0(@types/react@19.0.8)(react@19.0.0)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.18)(react@18.3.1) - react: 18.3.1 + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.8)(react@19.0.0) + react: 19.0.0 optionalDependencies: - '@types/react': 18.3.18 + '@types/react': 19.0.8 '@radix-ui/rect@1.1.0': {} - '@rollup/pluginutils@5.1.4(rollup@4.30.1)': + '@rollup/pluginutils@5.1.4(rollup@4.32.0)': dependencies: '@types/estree': 1.0.6 estree-walker: 2.0.2 picomatch: 4.0.2 optionalDependencies: - rollup: 4.30.1 + rollup: 4.32.0 - '@rollup/rollup-android-arm-eabi@4.30.1': + '@rollup/rollup-android-arm-eabi@4.32.0': optional: true - '@rollup/rollup-android-arm64@4.30.1': + '@rollup/rollup-android-arm64@4.32.0': optional: true - '@rollup/rollup-darwin-arm64@4.30.1': + '@rollup/rollup-darwin-arm64@4.32.0': optional: true - '@rollup/rollup-darwin-x64@4.30.1': + '@rollup/rollup-darwin-x64@4.32.0': optional: true - '@rollup/rollup-freebsd-arm64@4.30.1': + '@rollup/rollup-freebsd-arm64@4.32.0': optional: true - '@rollup/rollup-freebsd-x64@4.30.1': + '@rollup/rollup-freebsd-x64@4.32.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.30.1': + '@rollup/rollup-linux-arm-gnueabihf@4.32.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.30.1': + '@rollup/rollup-linux-arm-musleabihf@4.32.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.30.1': + '@rollup/rollup-linux-arm64-gnu@4.32.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.30.1': + '@rollup/rollup-linux-arm64-musl@4.32.0': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.30.1': + '@rollup/rollup-linux-loongarch64-gnu@4.32.0': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.30.1': + '@rollup/rollup-linux-powerpc64le-gnu@4.32.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.30.1': + '@rollup/rollup-linux-riscv64-gnu@4.32.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.30.1': + '@rollup/rollup-linux-s390x-gnu@4.32.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.30.1': + '@rollup/rollup-linux-x64-gnu@4.32.0': optional: true - '@rollup/rollup-linux-x64-musl@4.30.1': + '@rollup/rollup-linux-x64-musl@4.32.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.30.1': + '@rollup/rollup-win32-arm64-msvc@4.32.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.30.1': + '@rollup/rollup-win32-ia32-msvc@4.32.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.30.1': + '@rollup/rollup-win32-x64-msvc@4.32.0': optional: true + '@sentry-internal/tracing@7.120.3': + dependencies: + '@sentry/core': 7.120.3 + '@sentry/types': 7.120.3 + '@sentry/utils': 7.120.3 + + '@sentry/core@7.120.3': + dependencies: + '@sentry/types': 7.120.3 + '@sentry/utils': 7.120.3 + + '@sentry/integrations@7.120.3': + dependencies: + '@sentry/core': 7.120.3 + '@sentry/types': 7.120.3 + '@sentry/utils': 7.120.3 + localforage: 1.10.0 + + '@sentry/node@7.120.3': + dependencies: + '@sentry-internal/tracing': 7.120.3 + '@sentry/core': 7.120.3 + '@sentry/integrations': 7.120.3 + '@sentry/types': 7.120.3 + '@sentry/utils': 7.120.3 + + '@sentry/types@7.120.3': {} + + '@sentry/utils@7.120.3': + dependencies: + '@sentry/types': 7.120.3 + '@sinclair/typebox@0.27.8': {} '@sinonjs/commons@3.0.1': @@ -12723,128 +13373,128 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 - '@stackflow/config@1.2.0': {} + '@stackflow/config@1.2.1': {} - '@stackflow/core@1.1.0': + '@stackflow/core@1.1.1': dependencies: react-fast-compare: 3.2.2 - '@stackflow/link@1.6.0(@stackflow/core@1.1.0)(@stackflow/plugin-history-sync@1.7.0(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@stackflow/react@1.4.1(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@stackflow/plugin-preload@1.4.3(@stackflow/core@1.1.0)(@stackflow/plugin-history-sync@1.7.0(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@stackflow/react@1.4.1(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@stackflow/react@1.4.1(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@stackflow/react@1.4.1(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1)': + '@stackflow/link@1.6.1(@stackflow/core@1.1.1)(@stackflow/plugin-history-sync@1.7.1(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@stackflow/react@1.4.2(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@types/react@19.0.8)(react@19.0.0))(@types/react@19.0.8)(react@19.0.0))(@stackflow/plugin-preload@1.4.3(@stackflow/core@1.1.1)(@stackflow/plugin-history-sync@1.7.1(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@stackflow/react@1.4.2(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@types/react@19.0.8)(react@19.0.0))(@types/react@19.0.8)(react@19.0.0))(@stackflow/react@1.4.2(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@types/react@19.0.8)(react@19.0.0))(@types/react@19.0.8)(react@19.0.0))(@stackflow/react@1.4.2(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@types/react@19.0.8)(react@19.0.0))(@types/react@19.0.8)(react@19.0.0)': dependencies: - '@stackflow/core': 1.1.0 - '@stackflow/plugin-history-sync': 1.7.0(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@stackflow/react@1.4.1(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) - '@stackflow/plugin-preload': 1.4.3(@stackflow/core@1.1.0)(@stackflow/plugin-history-sync@1.7.0(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@stackflow/react@1.4.1(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@stackflow/react@1.4.1(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) - '@stackflow/react': 1.4.1(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@types/react@18.3.18)(react@18.3.1) - '@types/react': 18.3.18 - react: 18.3.1 + '@stackflow/core': 1.1.1 + '@stackflow/plugin-history-sync': 1.7.1(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@stackflow/react@1.4.2(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@types/react@19.0.8)(react@19.0.0))(@types/react@19.0.8)(react@19.0.0) + '@stackflow/plugin-preload': 1.4.3(@stackflow/core@1.1.1)(@stackflow/plugin-history-sync@1.7.1(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@stackflow/react@1.4.2(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@types/react@19.0.8)(react@19.0.0))(@types/react@19.0.8)(react@19.0.0))(@stackflow/react@1.4.2(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@types/react@19.0.8)(react@19.0.0))(@types/react@19.0.8)(react@19.0.0) + '@stackflow/react': 1.4.2(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@types/react@19.0.8)(react@19.0.0) + '@types/react': 19.0.8 + react: 19.0.0 - '@stackflow/plugin-basic-ui@1.11.1(@stackflow/core@1.1.0)(@stackflow/react@1.4.1(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(babel-plugin-macros@3.1.0)(react@18.3.1)': + '@stackflow/plugin-basic-ui@1.11.1(@stackflow/core@1.1.1)(@stackflow/react@1.4.2(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@types/react@19.0.8)(react@19.0.0))(@types/react@19.0.8)(babel-plugin-macros@3.1.0)(react@19.0.0)': dependencies: - '@stackflow/core': 1.1.0 - '@stackflow/react': 1.4.1(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@types/react@18.3.18)(react@18.3.1) - '@stackflow/react-ui-core': 1.2.1(@stackflow/core@1.1.0)(@stackflow/react@1.4.1(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) - '@types/react': 18.3.18 + '@stackflow/core': 1.1.1 + '@stackflow/react': 1.4.2(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@types/react@19.0.8)(react@19.0.0) + '@stackflow/react-ui-core': 1.2.1(@stackflow/core@1.1.1)(@stackflow/react@1.4.2(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@types/react@19.0.8)(react@19.0.0))(@types/react@19.0.8)(react@19.0.0) + '@types/react': 19.0.8 '@vanilla-extract/css': 1.17.0(babel-plugin-macros@3.1.0) '@vanilla-extract/dynamic': 2.1.2 '@vanilla-extract/private': 1.0.6 '@vanilla-extract/recipes': 0.5.5(@vanilla-extract/css@1.17.0(babel-plugin-macros@3.1.0)) - react: 18.3.1 + react: 19.0.0 transitivePeerDependencies: - babel-plugin-macros - '@stackflow/plugin-history-sync@1.7.0(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@stackflow/react@1.4.1(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1)': + '@stackflow/plugin-history-sync@1.7.1(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@stackflow/react@1.4.2(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@types/react@19.0.8)(react@19.0.0))(@types/react@19.0.8)(react@19.0.0)': dependencies: - '@stackflow/config': 1.2.0 - '@stackflow/core': 1.1.0 - '@stackflow/react': 1.4.1(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@types/react@18.3.18)(react@18.3.1) - '@types/react': 18.3.18 + '@stackflow/config': 1.2.1 + '@stackflow/core': 1.1.1 + '@stackflow/react': 1.4.2(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@types/react@19.0.8)(react@19.0.0) + '@types/react': 19.0.8 flatted: 3.3.2 history: 5.3.0 - react: 18.3.1 + react: 19.0.0 url-pattern: 1.0.3 - '@stackflow/plugin-preload@1.4.3(@stackflow/core@1.1.0)(@stackflow/plugin-history-sync@1.7.0(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@stackflow/react@1.4.1(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@stackflow/react@1.4.1(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1)': + '@stackflow/plugin-preload@1.4.3(@stackflow/core@1.1.1)(@stackflow/plugin-history-sync@1.7.1(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@stackflow/react@1.4.2(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@types/react@19.0.8)(react@19.0.0))(@types/react@19.0.8)(react@19.0.0))(@stackflow/react@1.4.2(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@types/react@19.0.8)(react@19.0.0))(@types/react@19.0.8)(react@19.0.0)': dependencies: - '@stackflow/core': 1.1.0 - '@stackflow/plugin-history-sync': 1.7.0(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@stackflow/react@1.4.1(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) - '@stackflow/react': 1.4.1(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@types/react@18.3.18)(react@18.3.1) - '@types/react': 18.3.18 - react: 18.3.1 + '@stackflow/core': 1.1.1 + '@stackflow/plugin-history-sync': 1.7.1(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@stackflow/react@1.4.2(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@types/react@19.0.8)(react@19.0.0))(@types/react@19.0.8)(react@19.0.0) + '@stackflow/react': 1.4.2(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@types/react@19.0.8)(react@19.0.0) + '@types/react': 19.0.8 + react: 19.0.0 - '@stackflow/plugin-renderer-basic@1.1.13(@stackflow/core@1.1.0)(@stackflow/react@1.4.1(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1)': + '@stackflow/plugin-renderer-basic@1.1.13(@stackflow/core@1.1.1)(@stackflow/react@1.4.2(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@types/react@19.0.8)(react@19.0.0))(@types/react@19.0.8)(react@19.0.0)': dependencies: - '@stackflow/core': 1.1.0 - '@stackflow/react': 1.4.1(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@types/react@18.3.18)(react@18.3.1) - '@types/react': 18.3.18 - react: 18.3.1 + '@stackflow/core': 1.1.1 + '@stackflow/react': 1.4.2(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@types/react@19.0.8)(react@19.0.0) + '@types/react': 19.0.8 + react: 19.0.0 - '@stackflow/react-ui-core@1.2.1(@stackflow/core@1.1.0)(@stackflow/react@1.4.1(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1)': + '@stackflow/react-ui-core@1.2.1(@stackflow/core@1.1.1)(@stackflow/react@1.4.2(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@types/react@19.0.8)(react@19.0.0))(@types/react@19.0.8)(react@19.0.0)': dependencies: - '@stackflow/core': 1.1.0 - '@stackflow/react': 1.4.1(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@types/react@18.3.18)(react@18.3.1) - '@types/react': 18.3.18 - react: 18.3.1 + '@stackflow/core': 1.1.1 + '@stackflow/react': 1.4.2(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@types/react@19.0.8)(react@19.0.0) + '@types/react': 19.0.8 + react: 19.0.0 - '@stackflow/react@1.4.1(@stackflow/config@1.2.0)(@stackflow/core@1.1.0)(@types/react@18.3.18)(react@18.3.1)': + '@stackflow/react@1.4.2(@stackflow/config@1.2.1)(@stackflow/core@1.1.1)(@types/react@19.0.8)(react@19.0.0)': dependencies: - '@stackflow/config': 1.2.0 - '@stackflow/core': 1.1.0 - '@types/react': 18.3.18 + '@stackflow/config': 1.2.1 + '@stackflow/core': 1.1.1 + '@types/react': 19.0.8 history: 5.3.0 - react: 18.3.1 + react: 19.0.0 react-fast-compare: 3.2.2 url-pattern: 1.0.3 - '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.26.0)': + '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 - '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.26.0)': + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 - '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.26.0)': + '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 - '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.26.0)': + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 - '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.26.0)': + '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 - '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.26.0)': + '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 - '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.26.0)': + '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 - '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.26.0)': + '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 - '@svgr/babel-preset@8.1.0(@babel/core@7.26.0)': + '@svgr/babel-preset@8.1.0(@babel/core@7.26.7)': dependencies: - '@babel/core': 7.26.0 - '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.26.0) - '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.26.0) - '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.26.0) - '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.26.0) - '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.26.0) - '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.26.0) - '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.26.0) - '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.26.0) + '@babel/core': 7.26.7 + '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.26.7) + '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.26.7) + '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.26.7) + '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.26.7) + '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.26.7) + '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.26.7) + '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.26.7) + '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.26.7) - '@svgr/core@8.1.0(typescript@5.4.5)': + '@svgr/core@8.1.0(typescript@5.7.3)': dependencies: - '@babel/core': 7.26.0 - '@svgr/babel-preset': 8.1.0(@babel/core@7.26.0) + '@babel/core': 7.26.7 + '@svgr/babel-preset': 8.1.0(@babel/core@7.26.7) camelcase: 6.3.0 - cosmiconfig: 8.3.6(typescript@5.4.5) + cosmiconfig: 8.3.6(typescript@5.7.3) snake-case: 3.0.4 transitivePeerDependencies: - supports-color @@ -12852,87 +13502,87 @@ snapshots: '@svgr/hast-util-to-babel-ast@8.0.0': dependencies: - '@babel/types': 7.26.5 + '@babel/types': 7.26.7 entities: 4.5.0 - '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.4.5))': + '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.7.3))': dependencies: - '@babel/core': 7.26.0 - '@svgr/babel-preset': 8.1.0(@babel/core@7.26.0) - '@svgr/core': 8.1.0(typescript@5.4.5) + '@babel/core': 7.26.7 + '@svgr/babel-preset': 8.1.0(@babel/core@7.26.7) + '@svgr/core': 8.1.0(typescript@5.7.3) '@svgr/hast-util-to-babel-ast': 8.0.0 svg-parser: 2.0.4 transitivePeerDependencies: - supports-color - '@svgr/plugin-svgo@8.1.0(@svgr/core@8.1.0(typescript@5.4.5))(typescript@5.4.5)': + '@svgr/plugin-svgo@8.1.0(@svgr/core@8.1.0(typescript@5.7.3))(typescript@5.7.3)': dependencies: - '@svgr/core': 8.1.0(typescript@5.4.5) - cosmiconfig: 8.3.6(typescript@5.4.5) + '@svgr/core': 8.1.0(typescript@5.7.3) + cosmiconfig: 8.3.6(typescript@5.7.3) deepmerge: 4.3.1 svgo: 3.3.2 transitivePeerDependencies: - typescript - '@svgr/webpack@8.1.0(typescript@5.4.5)': + '@svgr/webpack@8.1.0(typescript@5.7.3)': dependencies: - '@babel/core': 7.26.0 - '@babel/plugin-transform-react-constant-elements': 7.25.9(@babel/core@7.26.0) - '@babel/preset-env': 7.26.0(@babel/core@7.26.0) - '@babel/preset-react': 7.26.3(@babel/core@7.26.0) - '@babel/preset-typescript': 7.26.0(@babel/core@7.26.0) - '@svgr/core': 8.1.0(typescript@5.4.5) - '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.4.5)) - '@svgr/plugin-svgo': 8.1.0(@svgr/core@8.1.0(typescript@5.4.5))(typescript@5.4.5) + '@babel/core': 7.26.7 + '@babel/plugin-transform-react-constant-elements': 7.25.9(@babel/core@7.26.7) + '@babel/preset-env': 7.26.7(@babel/core@7.26.7) + '@babel/preset-react': 7.26.3(@babel/core@7.26.7) + '@babel/preset-typescript': 7.26.0(@babel/core@7.26.7) + '@svgr/core': 8.1.0(typescript@5.7.3) + '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.7.3)) + '@svgr/plugin-svgo': 8.1.0(@svgr/core@8.1.0(typescript@5.7.3))(typescript@5.7.3) transitivePeerDependencies: - supports-color - typescript - '@swc/core-darwin-arm64@1.10.7': + '@swc/core-darwin-arm64@1.10.11': optional: true - '@swc/core-darwin-x64@1.10.7': + '@swc/core-darwin-x64@1.10.11': optional: true - '@swc/core-linux-arm-gnueabihf@1.10.7': + '@swc/core-linux-arm-gnueabihf@1.10.11': optional: true - '@swc/core-linux-arm64-gnu@1.10.7': + '@swc/core-linux-arm64-gnu@1.10.11': optional: true - '@swc/core-linux-arm64-musl@1.10.7': + '@swc/core-linux-arm64-musl@1.10.11': optional: true - '@swc/core-linux-x64-gnu@1.10.7': + '@swc/core-linux-x64-gnu@1.10.11': optional: true - '@swc/core-linux-x64-musl@1.10.7': + '@swc/core-linux-x64-musl@1.10.11': optional: true - '@swc/core-win32-arm64-msvc@1.10.7': + '@swc/core-win32-arm64-msvc@1.10.11': optional: true - '@swc/core-win32-ia32-msvc@1.10.7': + '@swc/core-win32-ia32-msvc@1.10.11': optional: true - '@swc/core-win32-x64-msvc@1.10.7': + '@swc/core-win32-x64-msvc@1.10.11': optional: true - '@swc/core@1.10.7(@swc/helpers@0.5.15)': + '@swc/core@1.10.11(@swc/helpers@0.5.15)': dependencies: '@swc/counter': 0.1.3 '@swc/types': 0.1.17 optionalDependencies: - '@swc/core-darwin-arm64': 1.10.7 - '@swc/core-darwin-x64': 1.10.7 - '@swc/core-linux-arm-gnueabihf': 1.10.7 - '@swc/core-linux-arm64-gnu': 1.10.7 - '@swc/core-linux-arm64-musl': 1.10.7 - '@swc/core-linux-x64-gnu': 1.10.7 - '@swc/core-linux-x64-musl': 1.10.7 - '@swc/core-win32-arm64-msvc': 1.10.7 - '@swc/core-win32-ia32-msvc': 1.10.7 - '@swc/core-win32-x64-msvc': 1.10.7 + '@swc/core-darwin-arm64': 1.10.11 + '@swc/core-darwin-x64': 1.10.11 + '@swc/core-linux-arm-gnueabihf': 1.10.11 + '@swc/core-linux-arm64-gnu': 1.10.11 + '@swc/core-linux-arm64-musl': 1.10.11 + '@swc/core-linux-x64-gnu': 1.10.11 + '@swc/core-linux-x64-musl': 1.10.11 + '@swc/core-win32-arm64-msvc': 1.10.11 + '@swc/core-win32-ia32-msvc': 1.10.11 + '@swc/core-win32-x64-msvc': 1.10.11 '@swc/helpers': 0.5.15 '@swc/counter@0.1.3': {} @@ -12945,25 +13595,25 @@ snapshots: dependencies: '@swc/counter': 0.1.3 - '@tanstack/query-core@5.64.1': {} + '@tanstack/query-core@5.64.2': {} - '@tanstack/query-devtools@5.62.16': {} + '@tanstack/query-devtools@5.64.2': {} - '@tanstack/react-query-devtools@5.64.1(@tanstack/react-query@5.64.1(react@18.3.1))(react@18.3.1)': + '@tanstack/react-query-devtools@5.64.2(@tanstack/react-query@5.64.2(react@19.0.0))(react@19.0.0)': dependencies: - '@tanstack/query-devtools': 5.62.16 - '@tanstack/react-query': 5.64.1(react@18.3.1) - react: 18.3.1 + '@tanstack/query-devtools': 5.64.2 + '@tanstack/react-query': 5.64.2(react@19.0.0) + react: 19.0.0 - '@tanstack/react-query@5.64.1(react@18.3.1)': + '@tanstack/react-query@5.64.2(react@19.0.0)': dependencies: - '@tanstack/query-core': 5.64.1 - react: 18.3.1 + '@tanstack/query-core': 5.64.2 + react: 19.0.0 '@testing-library/dom@10.4.0': dependencies: '@babel/code-frame': 7.26.2 - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.26.7 '@types/aria-query': 5.0.4 aria-query: 5.3.0 chalk: 4.1.2 @@ -12981,35 +13631,37 @@ snapshots: lodash: 4.17.21 redent: 3.0.0 - '@testing-library/react-hooks@8.0.1(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@testing-library/react-hooks@8.0.1(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@babel/runtime': 7.26.0 - react: 18.3.1 - react-error-boundary: 3.1.4(react@18.3.1) + '@babel/runtime': 7.26.7 + react: 19.0.0 + react-error-boundary: 3.1.4(react@19.0.0) optionalDependencies: - '@types/react': 18.3.18 - react-dom: 18.3.1(react@18.3.1) + '@types/react': 19.0.8 + react-dom: 19.0.0(react@19.0.0) - '@testing-library/react@16.1.0(@testing-library/dom@10.4.0)(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@testing-library/react@16.2.0(@testing-library/dom@10.4.0)(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.26.7 '@testing-library/dom': 10.4.0 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) optionalDependencies: - '@types/react': 18.3.18 - '@types/react-dom': 18.3.5(@types/react@18.3.18) + '@types/react': 19.0.8 + '@types/react-dom': 19.0.3(@types/react@19.0.8) - '@testing-library/user-event@14.5.2(@testing-library/dom@10.4.0)': + '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.0)': dependencies: '@testing-library/dom': 10.4.0 '@tootallnate/once@2.0.0': {} + '@tootallnate/quickjs-emscripten@0.23.0': {} + '@trivago/prettier-plugin-sort-imports@4.3.0(prettier@3.4.2)': dependencies: '@babel/generator': 7.17.7 - '@babel/parser': 7.26.5 + '@babel/parser': 7.26.7 '@babel/traverse': 7.23.2 '@babel/types': 7.17.0 javascript-natural-sort: 0.7.1 @@ -13028,70 +13680,35 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.26.5 - '@babel/types': 7.26.5 + '@babel/parser': 7.26.7 + '@babel/types': 7.26.7 '@types/babel__generator': 7.6.8 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.20.6 '@types/babel__generator@7.6.8': dependencies: - '@babel/types': 7.26.5 + '@babel/types': 7.26.7 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.26.5 - '@babel/types': 7.26.5 + '@babel/parser': 7.26.7 + '@babel/types': 7.26.7 '@types/babel__traverse@7.20.6': dependencies: - '@babel/types': 7.26.5 - - '@types/body-parser@1.19.5': - dependencies: - '@types/connect': 3.4.38 - '@types/node': 20.13.0 - - '@types/connect@3.4.38': - dependencies: - '@types/node': 20.13.0 + '@babel/types': 7.26.7 '@types/cookie@0.6.0': {} - '@types/cors@2.8.17': - dependencies: - '@types/node': 20.13.0 - '@types/crypto-js@4.2.2': {} - '@types/eslint@9.6.1': - dependencies: - '@types/estree': 1.0.6 - '@types/json-schema': 7.0.15 - optional: true - '@types/estree@1.0.6': {} - '@types/express-serve-static-core@5.0.4': - dependencies: - '@types/node': 20.13.0 - '@types/qs': 6.9.18 - '@types/range-parser': 1.2.7 - '@types/send': 0.17.4 - - '@types/express@5.0.0': - dependencies: - '@types/body-parser': 1.19.5 - '@types/express-serve-static-core': 5.0.4 - '@types/qs': 6.9.18 - '@types/serve-static': 1.15.7 - '@types/graceful-fs@4.1.9': dependencies: '@types/node': 20.13.0 - '@types/http-errors@2.0.4': {} - '@types/istanbul-lib-coverage@2.0.6': {} '@types/istanbul-lib-report@3.0.3': @@ -13115,48 +13732,25 @@ snapshots: '@types/tough-cookie': 4.0.5 parse5: 7.2.1 - '@types/json-schema@7.0.15': - optional: true - - '@types/mime@1.3.5': {} - '@types/node@20.13.0': dependencies: undici-types: 5.26.5 '@types/parse-json@4.0.2': {} - '@types/prop-types@15.7.14': {} - - '@types/qs@6.9.18': {} - - '@types/range-parser@1.2.7': {} - - '@types/react-dom@18.3.5(@types/react@18.3.18)': + '@types/react-dom@19.0.3(@types/react@19.0.8)': dependencies: - '@types/react': 18.3.18 + '@types/react': 19.0.8 '@types/react-lazy-load-image-component@1.6.4': dependencies: - '@types/react': 18.3.18 + '@types/react': 19.0.8 csstype: 3.1.3 - '@types/react@18.3.18': + '@types/react@19.0.8': dependencies: - '@types/prop-types': 15.7.14 csstype: 3.1.3 - '@types/send@0.17.4': - dependencies: - '@types/mime': 1.3.5 - '@types/node': 20.13.0 - - '@types/serve-static@1.15.7': - dependencies: - '@types/http-errors': 2.0.4 - '@types/node': 20.13.0 - '@types/send': 0.17.4 - '@types/stack-utils@2.0.3': {} '@types/statuses@2.0.5': {} @@ -13169,34 +13763,39 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.4.5))(eslint@8.57.1)(typescript@5.4.5)': + '@types/yauzl@2.10.3': + dependencies: + '@types/node': 20.13.0 + optional: true + + '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.4.5) + '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.7.3) '@typescript-eslint/scope-manager': 7.18.0 - '@typescript-eslint/type-utils': 7.18.0(eslint@8.57.1)(typescript@5.4.5) - '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.4.5) + '@typescript-eslint/type-utils': 7.18.0(eslint@8.57.1)(typescript@5.7.3) + '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.7.3) '@typescript-eslint/visitor-keys': 7.18.0 eslint: 8.57.1 graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 - ts-api-utils: 1.4.3(typescript@5.4.5) + ts-api-utils: 1.4.3(typescript@5.7.3) optionalDependencies: - typescript: 5.4.5 + typescript: 5.7.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.4.5)': + '@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.3)': dependencies: '@typescript-eslint/scope-manager': 7.18.0 '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.4.5) + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.7.3) '@typescript-eslint/visitor-keys': 7.18.0 debug: 4.4.0 eslint: 8.57.1 optionalDependencies: - typescript: 5.4.5 + typescript: 5.7.3 transitivePeerDependencies: - supports-color @@ -13205,21 +13804,21 @@ snapshots: '@typescript-eslint/types': 7.18.0 '@typescript-eslint/visitor-keys': 7.18.0 - '@typescript-eslint/type-utils@7.18.0(eslint@8.57.1)(typescript@5.4.5)': + '@typescript-eslint/type-utils@7.18.0(eslint@8.57.1)(typescript@5.7.3)': dependencies: - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.4.5) - '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.4.5) + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.7.3) + '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.7.3) debug: 4.4.0 eslint: 8.57.1 - ts-api-utils: 1.4.3(typescript@5.4.5) + ts-api-utils: 1.4.3(typescript@5.7.3) optionalDependencies: - typescript: 5.4.5 + typescript: 5.7.3 transitivePeerDependencies: - supports-color '@typescript-eslint/types@7.18.0': {} - '@typescript-eslint/typescript-estree@7.18.0(typescript@5.4.5)': + '@typescript-eslint/typescript-estree@7.18.0(typescript@5.7.3)': dependencies: '@typescript-eslint/types': 7.18.0 '@typescript-eslint/visitor-keys': 7.18.0 @@ -13228,18 +13827,18 @@ snapshots: is-glob: 4.0.3 minimatch: 9.0.5 semver: 7.6.3 - ts-api-utils: 1.4.3(typescript@5.4.5) + ts-api-utils: 1.4.3(typescript@5.7.3) optionalDependencies: - typescript: 5.4.5 + typescript: 5.7.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@7.18.0(eslint@8.57.1)(typescript@5.4.5)': + '@typescript-eslint/utils@7.18.0(eslint@8.57.1)(typescript@5.7.3)': dependencies: '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) '@typescript-eslint/scope-manager': 7.18.0 '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.4.5) + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.7.3) eslint: 8.57.1 transitivePeerDependencies: - supports-color @@ -13250,7 +13849,7 @@ snapshots: '@typescript-eslint/types': 7.18.0 eslint-visitor-keys: 3.4.3 - '@ungap/structured-clone@1.2.1': {} + '@ungap/structured-clone@1.3.0': {} '@vanilla-extract/css@1.17.0(babel-plugin-macros@3.1.0)': dependencies: @@ -13279,14 +13878,14 @@ snapshots: dependencies: '@vanilla-extract/css': 1.17.0(babel-plugin-macros@3.1.0) - '@vitejs/plugin-react-swc@3.7.2(@swc/helpers@0.5.15)(vite@6.0.7(@types/node@20.13.0)(jiti@1.21.7)(terser@5.37.0)(yaml@2.7.0))': + '@vitejs/plugin-react-swc@3.7.2(@swc/helpers@0.5.15)(vite@6.0.11(@types/node@20.13.0)(jiti@1.21.7)(yaml@2.7.0))': dependencies: - '@swc/core': 1.10.7(@swc/helpers@0.5.15) - vite: 6.0.7(@types/node@20.13.0)(jiti@1.21.7)(terser@5.37.0)(yaml@2.7.0) + '@swc/core': 1.10.11(@swc/helpers@0.5.15) + vite: 6.0.11(@types/node@20.13.0)(jiti@1.21.7)(yaml@2.7.0) transitivePeerDependencies: - '@swc/helpers' - '@vitest/coverage-v8@2.1.8(vitest@2.1.8(@types/node@20.13.0)(@vitest/ui@2.1.8)(happy-dom@12.10.3)(jsdom@26.0.0)(msw@2.7.0(@types/node@20.13.0)(typescript@5.4.5))(terser@5.37.0))': + '@vitest/coverage-v8@2.1.8(vitest@2.1.8(@types/node@20.13.0)(@vitest/ui@2.1.8)(jsdom@26.0.0)(msw@2.7.0(@types/node@20.13.0)(typescript@5.7.3)))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 0.2.3 @@ -13300,7 +13899,7 @@ snapshots: std-env: 3.8.0 test-exclude: 7.0.1 tinyrainbow: 1.2.0 - vitest: 2.1.8(@types/node@20.13.0)(@vitest/ui@2.1.8)(happy-dom@12.10.3)(jsdom@26.0.0)(msw@2.7.0(@types/node@20.13.0)(typescript@5.4.5))(terser@5.37.0) + vitest: 2.1.8(@types/node@20.13.0)(@vitest/ui@2.1.8)(jsdom@26.0.0)(msw@2.7.0(@types/node@20.13.0)(typescript@5.7.3)) transitivePeerDependencies: - supports-color @@ -13311,14 +13910,14 @@ snapshots: chai: 5.1.2 tinyrainbow: 1.2.0 - '@vitest/mocker@2.1.8(msw@2.7.0(@types/node@20.13.0)(typescript@5.4.5))(vite@5.4.11(@types/node@20.13.0)(terser@5.37.0))': + '@vitest/mocker@2.1.8(msw@2.7.0(@types/node@20.13.0)(typescript@5.7.3))(vite@5.4.14(@types/node@20.13.0))': dependencies: '@vitest/spy': 2.1.8 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - msw: 2.7.0(@types/node@20.13.0)(typescript@5.4.5) - vite: 5.4.11(@types/node@20.13.0)(terser@5.37.0) + msw: 2.7.0(@types/node@20.13.0)(typescript@5.7.3) + vite: 5.4.14(@types/node@20.13.0) '@vitest/pretty-format@2.1.8': dependencies: @@ -13348,7 +13947,7 @@ snapshots: sirv: 3.0.0 tinyglobby: 0.2.10 tinyrainbow: 1.2.0 - vitest: 2.1.8(@types/node@20.13.0)(@vitest/ui@2.1.8)(happy-dom@12.10.3)(jsdom@26.0.0)(msw@2.7.0(@types/node@20.13.0)(typescript@5.4.5))(terser@5.37.0) + vitest: 2.1.8(@types/node@20.13.0)(@vitest/ui@2.1.8)(jsdom@26.0.0)(msw@2.7.0(@types/node@20.13.0)(typescript@5.7.3)) '@vitest/utils@2.1.8': dependencies: @@ -13369,11 +13968,6 @@ snapshots: abab@2.0.6: {} - accepts@1.3.8: - dependencies: - mime-types: 2.1.35 - negotiator: 0.6.3 - acorn-globals@7.0.1: dependencies: acorn: 8.14.0 @@ -13452,14 +14046,12 @@ snapshots: call-bound: 1.0.3 is-array-buffer: 3.0.5 - array-flatten@1.1.1: {} - array-includes@3.1.8: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 es-abstract: 1.23.9 - es-object-atoms: 1.0.1 + es-object-atoms: 1.1.1 get-intrinsic: 1.2.7 is-string: 1.1.1 @@ -13471,7 +14063,7 @@ snapshots: define-properties: 1.2.1 es-abstract: 1.23.9 es-errors: 1.3.0 - es-object-atoms: 1.0.1 + es-object-atoms: 1.1.1 es-shim-unscopables: 1.0.2 array.prototype.flat@1.3.3: @@ -13510,26 +14102,60 @@ snapshots: ast-metadata-inferer@0.8.1: dependencies: - '@mdn/browser-compat-data': 5.6.29 + '@mdn/browser-compat-data': 5.6.33 + + ast-types@0.13.4: + dependencies: + tslib: 2.8.1 + + async-function@1.0.0: {} async@3.2.6: {} asynckit@0.4.0: {} - autoprefixer@10.4.20(postcss@8.5.0): + autocannon@8.0.0: + dependencies: + '@minimistjs/subarg': 1.0.0 + chalk: 4.1.2 + char-spinner: 1.0.1 + cli-table3: 0.6.5 + color-support: 1.1.3 + cross-argv: 2.0.0 + form-data: 4.0.1 + has-async-hooks: 1.0.0 + hdr-histogram-js: 3.0.0 + hdr-histogram-percentiles-obj: 3.0.0 + http-parser-js: 0.5.9 + hyperid: 3.3.0 + lodash.chunk: 4.2.0 + lodash.clonedeep: 4.5.0 + lodash.flatten: 4.4.0 + manage-path: 2.0.0 + on-net-listen: 1.1.2 + pretty-bytes: 5.6.0 + progress: 2.0.3 + reinterval: 1.1.0 + retimer: 3.0.0 + semver: 7.6.3 + timestring: 6.0.0 + + autoprefixer@10.4.20(postcss@8.5.1): dependencies: browserslist: 4.24.4 - caniuse-lite: 1.0.30001692 + caniuse-lite: 1.0.30001695 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.1.1 - postcss: 8.5.0 + postcss: 8.5.1 postcss-value-parser: 4.2.0 available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.0.0 + axe-core@4.10.2: {} + axios@1.7.9: dependencies: follow-redirects: 1.15.9 @@ -13538,13 +14164,15 @@ snapshots: transitivePeerDependencies: - debug - babel-jest@29.7.0(@babel/core@7.26.0): + b4a@1.6.7: {} + + babel-jest@29.7.0(@babel/core@7.26.7): dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@jest/transform': 29.7.0 '@types/babel__core': 7.20.5 babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.6.3(@babel/core@7.26.0) + babel-preset-jest: 29.6.3(@babel/core@7.26.7) chalk: 4.1.2 graceful-fs: 4.2.11 slash: 3.0.0 @@ -13564,69 +14192,98 @@ snapshots: babel-plugin-jest-hoist@29.6.3: dependencies: '@babel/template': 7.25.9 - '@babel/types': 7.26.5 + '@babel/types': 7.26.7 '@types/babel__core': 7.20.5 '@types/babel__traverse': 7.20.6 babel-plugin-macros@3.1.0: dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.26.7 cosmiconfig: 7.1.0 resolve: 1.22.10 - babel-plugin-polyfill-corejs2@0.4.12(@babel/core@7.26.0): + babel-plugin-polyfill-corejs2@0.4.12(@babel/core@7.26.7): dependencies: '@babel/compat-data': 7.26.5 - '@babel/core': 7.26.0 - '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.26.0) + '@babel/core': 7.26.7 + '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.26.7) semver: 6.3.1 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-corejs3@0.10.6(@babel/core@7.26.0): + babel-plugin-polyfill-corejs3@0.10.6(@babel/core@7.26.7): dependencies: - '@babel/core': 7.26.0 - '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.26.0) + '@babel/core': 7.26.7 + '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.26.7) core-js-compat: 3.40.0 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-regenerator@0.6.3(@babel/core@7.26.0): + babel-plugin-polyfill-regenerator@0.6.3(@babel/core@7.26.7): dependencies: - '@babel/core': 7.26.0 - '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.26.0) + '@babel/core': 7.26.7 + '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.26.7) transitivePeerDependencies: - supports-color - babel-preset-current-node-syntax@1.1.0(@babel/core@7.26.0): - dependencies: - '@babel/core': 7.26.0 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.26.0) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.26.0) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.26.0) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.26.0) - '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.0) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.26.0) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.26.0) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.26.0) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.26.0) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.26.0) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.26.0) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.26.0) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.26.0) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.26.0) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.26.0) - - babel-preset-jest@29.6.3(@babel/core@7.26.0): - dependencies: - '@babel/core': 7.26.0 + babel-preset-current-node-syntax@1.1.0(@babel/core@7.26.7): + dependencies: + '@babel/core': 7.26.7 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.26.7) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.26.7) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.26.7) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.26.7) + '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.7) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.26.7) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.26.7) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.26.7) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.26.7) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.26.7) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.26.7) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.26.7) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.26.7) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.26.7) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.26.7) + + babel-preset-jest@29.6.3(@babel/core@7.26.7): + dependencies: + '@babel/core': 7.26.7 babel-plugin-jest-hoist: 29.6.3 - babel-preset-current-node-syntax: 1.1.0(@babel/core@7.26.0) + babel-preset-current-node-syntax: 1.1.0(@babel/core@7.26.7) balanced-match@1.0.2: {} + bare-events@2.5.4: + optional: true + + bare-fs@4.0.1: + dependencies: + bare-events: 2.5.4 + bare-path: 3.0.0 + bare-stream: 2.6.4(bare-events@2.5.4) + transitivePeerDependencies: + - bare-buffer + optional: true + + bare-os@3.4.0: + optional: true + + bare-path@3.0.0: + dependencies: + bare-os: 3.4.0 + optional: true + + bare-stream@2.6.4(bare-events@2.5.4): + dependencies: + streamx: 2.22.0 + optionalDependencies: + bare-events: 2.5.4 + optional: true + base64-js@1.5.1: {} + basic-ftp@5.0.5: {} + binary-extensions@2.3.0: {} bl@4.1.0: @@ -13635,23 +14292,6 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 - body-parser@1.20.3: - dependencies: - bytes: 3.1.2 - content-type: 1.0.5 - debug: 2.6.9 - depd: 2.0.0 - destroy: 1.2.0 - http-errors: 2.0.0 - iconv-lite: 0.4.24 - on-finished: 2.4.1 - qs: 6.13.0 - raw-body: 2.5.2 - type-is: 1.6.18 - unpipe: 1.0.0 - transitivePeerDependencies: - - supports-color - boolbase@1.0.0: {} brace-expansion@1.1.11: @@ -13669,8 +14309,8 @@ snapshots: browserslist@4.24.4: dependencies: - caniuse-lite: 1.0.30001692 - electron-to-chromium: 1.5.80 + caniuse-lite: 1.0.30001695 + electron-to-chromium: 1.5.88 node-releases: 2.0.19 update-browserslist-db: 1.1.2(browserslist@4.24.4) @@ -13682,6 +14322,8 @@ snapshots: dependencies: node-int64: 0.4.0 + buffer-crc32@0.2.13: {} + buffer-from@1.1.2: {} buffer@5.7.1: @@ -13693,8 +14335,6 @@ snapshots: dependencies: streamsearch: 1.1.0 - bytes@3.1.2: {} - cac@6.7.14: {} call-bind-apply-helpers@1.0.1: @@ -13722,7 +14362,7 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001692: {} + caniuse-lite@1.0.30001695: {} chai@5.1.2: dependencies: @@ -13744,6 +14384,8 @@ snapshots: char-regex@1.0.2: {} + char-spinner@1.0.1: {} + check-error@2.1.1: {} chokidar@3.6.0: @@ -13758,13 +14400,30 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - ci-info@3.9.0: {} + chrome-launcher@1.1.2: + dependencies: + '@types/node': 20.13.0 + escape-string-regexp: 4.0.0 + is-wsl: 2.2.0 + lighthouse-logger: 2.0.1 + transitivePeerDependencies: + - supports-color - cjs-module-lexer@1.4.1: {} + chromium-bidi@0.11.0(devtools-protocol@0.0.1367902): + dependencies: + devtools-protocol: 0.0.1367902 + mitt: 3.0.1 + zod: 3.23.8 - class-variance-authority@0.7.1: + chromium-bidi@1.1.0(devtools-protocol@0.0.1380148): dependencies: - clsx: 2.1.1 + devtools-protocol: 0.0.1380148 + mitt: 3.0.1 + zod: 3.24.1 + + ci-info@3.9.0: {} + + cjs-module-lexer@1.4.1: {} cli-cursor@3.1.0: dependencies: @@ -13772,6 +14431,12 @@ snapshots: cli-spinners@2.6.1: {} + cli-table3@0.6.5: + dependencies: + string-width: 4.2.3 + optionalDependencies: + '@colors/colors': 1.5.0 + cli-width@4.1.0: {} client-only@0.0.1: {} @@ -13800,55 +14465,43 @@ snapshots: dependencies: color-name: 1.1.4 simple-swizzle: 0.2.2 - optional: true + + color-support@1.1.3: {} color@4.2.3: dependencies: color-convert: 2.0.1 color-string: 1.9.1 - optional: true combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 - commander@2.20.3: - optional: true - commander@4.1.1: {} commander@7.2.0: {} concat-map@0.0.1: {} - content-disposition@0.5.4: + configstore@5.0.1: dependencies: - safe-buffer: 5.2.1 - - content-type@1.0.5: {} + dot-prop: 5.3.0 + graceful-fs: 4.2.11 + make-dir: 3.1.0 + unique-string: 2.0.0 + write-file-atomic: 3.0.3 + xdg-basedir: 4.0.0 convert-source-map@1.9.0: {} convert-source-map@2.0.0: {} - cookie-signature@1.0.6: {} - - cookie@0.7.1: {} - cookie@0.7.2: {} - cookie@1.0.2: - optional: true - core-js-compat@3.40.0: dependencies: browserslist: 4.24.4 - cors@2.8.5: - dependencies: - object-assign: 4.1.1 - vary: 1.1.2 - cosmiconfig@7.1.0: dependencies: '@types/parse-json': 4.0.2 @@ -13857,14 +14510,23 @@ snapshots: path-type: 4.0.0 yaml: 1.10.2 - cosmiconfig@8.3.6(typescript@5.4.5): + cosmiconfig@8.3.6(typescript@5.7.3): dependencies: import-fresh: 3.3.0 js-yaml: 4.1.0 parse-json: 5.2.0 path-type: 4.0.0 optionalDependencies: - typescript: 5.4.5 + typescript: 5.7.3 + + cosmiconfig@9.0.0(typescript@5.7.3): + dependencies: + env-paths: 2.2.1 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + parse-json: 5.2.0 + optionalDependencies: + typescript: 5.7.3 create-jest@29.7.0(@types/node@20.13.0)(babel-plugin-macros@3.1.0): dependencies: @@ -13881,6 +14543,8 @@ snapshots: - supports-color - ts-node + cross-argv@2.0.0: {} + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -13889,6 +14553,10 @@ snapshots: crypto-js@4.2.0: {} + crypto-random-string@2.0.0: {} + + csp_evaluator@1.1.1: {} + css-select@5.1.0: dependencies: boolbase: 1.0.0 @@ -13927,11 +14595,13 @@ snapshots: cssstyle@4.2.1: dependencies: - '@asamuzakjp/css-color': 2.8.2 + '@asamuzakjp/css-color': 2.8.3 rrweb-cssom: 0.8.0 csstype@3.1.3: {} + data-uri-to-buffer@6.0.2: {} + data-urls@3.0.2: dependencies: abab: 2.0.6 @@ -13963,6 +14633,8 @@ snapshots: date-fns@4.1.0: {} + debounce@1.2.1: {} + debug@2.6.9: dependencies: ms: 2.0.0 @@ -13971,9 +14643,7 @@ snapshots: dependencies: ms: 2.1.3 - decimal.js@10.4.3: {} - - decode-uri-component@0.4.1: {} + decimal.js@10.5.0: {} dedent@1.5.3(babel-plugin-macros@3.1.0): optionalDependencies: @@ -14005,21 +14675,28 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 - delayed-stream@1.0.0: {} + degenerator@5.0.1: + dependencies: + ast-types: 0.13.4 + escodegen: 2.1.0 + esprima: 4.0.1 - depd@2.0.0: {} + delayed-stream@1.0.0: {} dequal@2.0.3: {} - destroy@1.2.0: {} - - detect-libc@2.0.3: - optional: true + detect-libc@2.0.3: {} detect-newline@3.1.0: {} detect-node-es@1.1.0: {} + devtools-protocol@0.0.1312386: {} + + devtools-protocol@0.0.1367902: {} + + devtools-protocol@0.0.1380148: {} + didyoumean@1.2.2: {} diff-sequences@29.6.3: {} @@ -14069,6 +14746,10 @@ snapshots: no-case: 3.0.4 tslib: 2.8.1 + dot-prop@5.3.0: + dependencies: + is-obj: 2.0.0 + dotenv-expand@11.0.7: dependencies: dotenv: 16.4.7 @@ -14081,21 +14762,21 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - eastasianwidth@0.2.0: {} + duplexer@0.1.2: {} - ee-first@1.1.1: {} + eastasianwidth@0.2.0: {} ejs@3.1.10: dependencies: jake: 10.9.2 - electron-to-chromium@1.5.80: {} + electron-to-chromium@1.5.88: {} - embla-carousel-react@8.5.2(react@18.3.1): + embla-carousel-react@8.5.2(react@19.0.0): dependencies: embla-carousel: 8.5.2 embla-carousel-reactive-utils: 8.5.2(embla-carousel@8.5.2) - react: 18.3.1 + react: 19.0.0 embla-carousel-reactive-utils@8.5.2(embla-carousel@8.5.2): dependencies: @@ -14109,10 +14790,6 @@ snapshots: emoji-regex@9.2.2: {} - encodeurl@1.0.2: {} - - encodeurl@2.0.0: {} - end-of-stream@1.4.4: dependencies: once: 1.4.0 @@ -14123,6 +14800,8 @@ snapshots: entities@4.5.0: {} + env-paths@2.2.1: {} + error-ex@1.3.2: dependencies: is-arrayish: 0.2.1 @@ -14139,7 +14818,7 @@ snapshots: data-view-byte-offset: 1.0.1 es-define-property: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.0.1 + es-object-atoms: 1.1.1 es-set-tostringtag: 2.1.0 es-to-primitive: 1.3.0 function.prototype.name: 1.1.8 @@ -14206,7 +14885,7 @@ snapshots: es-module-lexer@1.6.0: {} - es-object-atoms@1.0.1: + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -14308,8 +14987,6 @@ snapshots: escalade@3.2.0: {} - escape-html@1.0.3: {} - escape-string-regexp@1.0.5: {} escape-string-regexp@2.0.0: {} @@ -14330,24 +15007,23 @@ snapshots: eslint-plugin-compat@6.0.2(eslint@8.57.1): dependencies: - '@mdn/browser-compat-data': 5.6.29 + '@mdn/browser-compat-data': 5.6.33 ast-metadata-inferer: 0.8.1 browserslist: 4.24.4 - caniuse-lite: 1.0.30001692 + caniuse-lite: 1.0.30001695 eslint: 8.57.1 find-up: 5.0.0 globals: 15.14.0 lodash.memoize: 4.1.2 semver: 7.6.3 - eslint-plugin-prettier@5.2.1(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@8.57.1))(eslint@8.57.1)(prettier@3.4.2): + eslint-plugin-prettier@5.2.3(eslint-config-prettier@9.1.0(eslint@8.57.1))(eslint@8.57.1)(prettier@3.4.2): dependencies: eslint: 8.57.1 prettier: 3.4.2 prettier-linter-helpers: 1.0.0 synckit: 0.9.2 optionalDependencies: - '@types/eslint': 9.6.1 eslint-config-prettier: 9.1.0(eslint@8.57.1) eslint-plugin-react@7.37.4(eslint@8.57.1): @@ -14372,11 +15048,11 @@ snapshots: string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 - eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.4.5))(eslint@8.57.1)(typescript@5.4.5))(eslint@8.57.1): + eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1): dependencies: eslint: 8.57.1 optionalDependencies: - '@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.4.5))(eslint@8.57.1)(typescript@5.4.5) + '@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3) eslint-scope@7.2.2: dependencies: @@ -14394,7 +15070,7 @@ snapshots: '@humanwhocodes/config-array': 0.13.0 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 - '@ungap/structured-clone': 1.2.1 + '@ungap/structured-clone': 1.3.0 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 @@ -14454,8 +15130,6 @@ snapshots: esutils@2.0.3: {} - etag@1.8.1: {} - execa@5.1.1: dependencies: cross-spawn: 7.0.6 @@ -14480,39 +15154,13 @@ snapshots: jest-message-util: 29.7.0 jest-util: 29.7.0 - express@4.21.2: + extract-zip@2.0.1: dependencies: - accepts: 1.3.8 - array-flatten: 1.1.1 - body-parser: 1.20.3 - content-disposition: 0.5.4 - content-type: 1.0.5 - cookie: 0.7.1 - cookie-signature: 1.0.6 - debug: 2.6.9 - depd: 2.0.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - finalhandler: 1.3.1 - fresh: 0.5.2 - http-errors: 2.0.0 - merge-descriptors: 1.0.3 - methods: 1.1.2 - on-finished: 2.4.1 - parseurl: 1.3.3 - path-to-regexp: 0.1.12 - proxy-addr: 2.0.7 - qs: 6.13.0 - range-parser: 1.2.1 - safe-buffer: 5.2.1 - send: 0.19.0 - serve-static: 1.16.2 - setprototypeof: 1.2.0 - statuses: 2.0.1 - type-is: 1.6.18 - utils-merge: 1.0.1 - vary: 1.1.2 + debug: 4.4.0 + get-stream: 5.2.0 + yauzl: 2.10.0 + optionalDependencies: + '@types/yauzl': 2.10.3 transitivePeerDependencies: - supports-color @@ -14520,6 +15168,8 @@ snapshots: fast-diff@1.3.0: {} + fast-fifo@1.3.2: {} + fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -14540,7 +15190,11 @@ snapshots: dependencies: bser: 2.1.1 - fdir@6.4.2(picomatch@4.0.2): + fd-slicer@1.1.0: + dependencies: + pend: 1.2.0 + + fdir@6.4.3(picomatch@4.0.2): optionalDependencies: picomatch: 4.0.2 @@ -14562,20 +15216,6 @@ snapshots: dependencies: to-regex-range: 5.0.1 - filter-obj@5.1.0: {} - - finalhandler@1.3.1: - dependencies: - debug: 2.6.9 - encodeurl: 2.0.0 - escape-html: 1.0.3 - on-finished: 2.4.1 - parseurl: 1.3.3 - statuses: 2.0.1 - unpipe: 1.0.0 - transitivePeerDependencies: - - supports-color - find-root@1.1.0: {} find-up@4.1.0: @@ -14600,7 +15240,7 @@ snapshots: follow-redirects@1.15.9: {} - for-each@0.3.3: + for-each@0.3.4: dependencies: is-callable: 1.2.7 @@ -14615,21 +15255,17 @@ snapshots: combined-stream: 1.0.8 mime-types: 2.1.35 - forwarded@0.2.0: {} - fraction.js@4.3.7: {} - framer-motion@11.18.0(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + framer-motion@11.18.2(@emotion/is-prop-valid@1.3.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: - motion-dom: 11.16.4 - motion-utils: 11.16.0 + motion-dom: 11.18.1 + motion-utils: 11.18.1 tslib: 2.8.1 optionalDependencies: '@emotion/is-prop-valid': 1.3.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - fresh@0.5.2: {} + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) front-matter@4.0.2: dependencies: @@ -14667,7 +15303,7 @@ snapshots: call-bind-apply-helpers: 1.0.1 es-define-property: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.0.1 + es-object-atoms: 1.1.1 function-bind: 1.1.2 get-proto: 1.0.1 gopd: 1.2.0 @@ -14682,7 +15318,11 @@ snapshots: get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 - es-object-atoms: 1.0.1 + es-object-atoms: 1.1.1 + + get-stream@5.2.0: + dependencies: + pump: 3.0.2 get-stream@6.0.1: {} @@ -14692,6 +15332,14 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.2.7 + get-uri@6.0.4: + dependencies: + basic-ftp: 5.0.5 + data-uri-to-buffer: 6.0.2 + debug: 4.4.0 + transitivePeerDependencies: + - supports-color + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -14748,15 +15396,11 @@ snapshots: graphql@16.10.0: {} - happy-dom@12.10.3: + gzip-size@6.0.0: dependencies: - css.escape: 1.5.1 - entities: 4.5.0 - iconv-lite: 0.6.3 - webidl-conversions: 7.0.0 - whatwg-encoding: 2.0.0 - whatwg-mimetype: 3.0.0 - optional: true + duplexer: 0.1.2 + + has-async-hooks@1.0.0: {} has-bigints@1.1.0: {} @@ -14780,11 +15424,19 @@ snapshots: dependencies: function-bind: 1.1.2 + hdr-histogram-js@3.0.0: + dependencies: + '@assemblyscript/loader': 0.19.23 + base64-js: 1.5.1 + pako: 1.0.11 + + hdr-histogram-percentiles-obj@3.0.0: {} + headers-polyfill@4.0.3: {} history@5.3.0: dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.26.7 hoist-non-react-statics@3.3.2: dependencies: @@ -14800,13 +15452,9 @@ snapshots: html-escaper@2.0.2: {} - http-errors@2.0.0: - dependencies: - depd: 2.0.0 - inherits: 2.0.4 - setprototypeof: 1.2.0 - statuses: 2.0.1 - toidentifier: 1.0.1 + http-link-header@1.1.3: {} + + http-parser-js@0.5.9: {} http-proxy-agent@5.0.0: dependencies: @@ -14841,9 +15489,11 @@ snapshots: husky@9.1.7: {} - iconv-lite@0.4.24: + hyperid@3.3.0: dependencies: - safer-buffer: 2.1.2 + buffer: 5.7.1 + uuid: 8.3.2 + uuid-parse: 1.1.0 iconv-lite@0.6.3: dependencies: @@ -14853,8 +15503,9 @@ snapshots: ignore@5.3.2: {} - immer@9.0.21: - optional: true + image-ssim@0.2.0: {} + + immediate@3.0.6: {} import-fresh@3.3.0: dependencies: @@ -14883,7 +15534,17 @@ snapshots: hasown: 2.0.2 side-channel: 1.1.0 - ipaddr.js@1.9.1: {} + intl-messageformat@10.7.14: + dependencies: + '@formatjs/ecma402-abstract': 2.3.2 + '@formatjs/fast-memoize': 2.2.6 + '@formatjs/icu-messageformat-parser': 2.11.0 + tslib: 2.8.1 + + ip-address@9.0.5: + dependencies: + jsbn: 1.1.0 + sprintf-js: 1.1.3 is-array-buffer@3.0.5: dependencies: @@ -14893,11 +15554,11 @@ snapshots: is-arrayish@0.2.1: {} - is-arrayish@0.3.2: - optional: true + is-arrayish@0.3.2: {} - is-async-function@2.1.0: + is-async-function@2.1.1: dependencies: + async-function: 1.0.0 call-bound: 1.0.3 get-proto: 1.0.1 has-tostringtag: 1.0.2 @@ -14969,8 +15630,12 @@ snapshots: is-number@7.0.0: {} + is-obj@2.0.0: {} + is-path-inside@3.0.3: {} + is-plain-object@5.0.0: {} + is-potential-custom-element-name@1.0.1: {} is-regex@1.2.1: @@ -15003,6 +15668,8 @@ snapshots: dependencies: which-typed-array: 1.1.18 + is-typedarray@1.0.0: {} + is-unicode-supported@0.1.0: {} is-weakmap@2.0.2: {} @@ -15030,8 +15697,8 @@ snapshots: istanbul-lib-instrument@5.2.1: dependencies: - '@babel/core': 7.26.0 - '@babel/parser': 7.26.5 + '@babel/core': 7.26.7 + '@babel/parser': 7.26.7 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 6.3.1 @@ -15040,8 +15707,8 @@ snapshots: istanbul-lib-instrument@6.0.3: dependencies: - '@babel/core': 7.26.0 - '@babel/parser': 7.26.5 + '@babel/core': 7.26.7 + '@babel/parser': 7.26.7 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 7.6.3 @@ -15078,7 +15745,7 @@ snapshots: iterator.prototype@1.1.5: dependencies: define-data-property: 1.1.4 - es-object-atoms: 1.0.1 + es-object-atoms: 1.1.1 get-intrinsic: 1.2.7 get-proto: 1.0.1 has-symbols: 1.1.0 @@ -15152,10 +15819,10 @@ snapshots: jest-config@29.7.0(@types/node@20.13.0)(babel-plugin-macros@3.1.0): dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.26.0) + babel-jest: 29.7.0(@babel/core@7.26.7) chalk: 4.1.2 ci-info: 3.9.0 deepmerge: 4.3.1 @@ -15351,15 +16018,15 @@ snapshots: jest-snapshot@29.7.0: dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@babel/generator': 7.26.5 - '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.0) - '@babel/types': 7.26.5 + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.7) + '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.7) + '@babel/types': 7.26.7 '@jest/expect-utils': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - babel-preset-current-node-syntax: 1.1.0(@babel/core@7.26.0) + babel-preset-current-node-syntax: 1.1.0(@babel/core@7.26.7) chalk: 4.1.2 expect: 29.7.0 graceful-fs: 4.2.11 @@ -15424,8 +16091,12 @@ snapshots: jiti@1.21.7: {} + jpeg-js@0.4.4: {} + js-cookie@3.0.5: {} + js-library-detector@6.7.0: {} + js-tokens@4.0.0: {} js-yaml@3.14.1: @@ -15437,6 +16108,8 @@ snapshots: dependencies: argparse: 2.0.1 + jsbn@1.1.0: {} + jsdom@20.0.3: dependencies: abab: 2.0.6 @@ -15445,7 +16118,7 @@ snapshots: cssom: 0.5.0 cssstyle: 2.3.0 data-urls: 3.0.2 - decimal.js: 10.4.3 + decimal.js: 10.5.0 domexception: 4.0.0 escodegen: 2.1.0 form-data: 4.0.1 @@ -15474,7 +16147,7 @@ snapshots: dependencies: cssstyle: 4.2.1 data-urls: 5.0.0 - decimal.js: 10.4.3 + decimal.js: 10.5.0 form-data: 4.0.1 html-encoding-sniffer: 4.0.0 http-proxy-agent: 7.0.2 @@ -15540,16 +16213,71 @@ snapshots: lexical@0.23.1: {} + lexical@0.27.1: {} + lib0@0.2.99: dependencies: isomorphic.js: 0.2.5 + lie@3.1.1: + dependencies: + immediate: 3.0.6 + + lighthouse-logger@2.0.1: + dependencies: + debug: 2.6.9 + marky: 1.2.5 + transitivePeerDependencies: + - supports-color + + lighthouse-stack-packs@1.12.2: {} + + lighthouse@12.3.0: + dependencies: + '@paulirish/trace_engine': 0.0.39 + '@sentry/node': 7.120.3 + axe-core: 4.10.2 + chrome-launcher: 1.1.2 + configstore: 5.0.1 + csp_evaluator: 1.1.1 + devtools-protocol: 0.0.1312386 + enquirer: 2.3.6 + http-link-header: 1.1.3 + intl-messageformat: 10.7.14 + jpeg-js: 0.4.4 + js-library-detector: 6.7.0 + lighthouse-logger: 2.0.1 + lighthouse-stack-packs: 1.12.2 + lodash-es: 4.17.21 + lookup-closest-locale: 6.2.0 + metaviewport-parser: 0.3.0 + open: 8.4.2 + parse-cache-control: 1.0.1 + puppeteer-core: 23.11.1 + robots-parser: 3.0.1 + semver: 5.7.2 + speedline-core: 1.4.3 + third-party-web: 0.26.2 + tldts-icann: 6.1.76 + ws: 7.5.10 + yargs: 17.7.2 + yargs-parser: 21.1.1 + transitivePeerDependencies: + - bare-buffer + - bufferutil + - supports-color + - utf-8-validate + lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} lines-and-columns@2.0.3: {} + localforage@1.10.0: + dependencies: + lie: 3.1.1 + locate-path@5.0.0: dependencies: p-locate: 4.1.0 @@ -15558,8 +16286,16 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash-es@4.17.21: {} + + lodash.chunk@4.2.0: {} + + lodash.clonedeep@4.5.0: {} + lodash.debounce@4.0.8: {} + lodash.flatten@4.4.0: {} + lodash.memoize@4.1.2: {} lodash.merge@4.6.2: {} @@ -15573,6 +16309,8 @@ snapshots: chalk: 4.1.2 is-unicode-supported: 0.1.0 + lookup-closest-locale@6.2.0: {} + loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 @@ -15587,15 +16325,15 @@ snapshots: lru-cache@10.4.3: {} - lru-cache@11.0.2: {} - lru-cache@5.1.1: dependencies: yallist: 3.1.1 - lucide-react@0.460.0(react@18.3.1): + lru-cache@7.18.3: {} + + lucide-react@0.460.0(react@19.0.0): dependencies: - react: 18.3.1 + react: 19.0.0 lz-string@1.5.0: {} @@ -15605,10 +16343,14 @@ snapshots: magicast@0.3.5: dependencies: - '@babel/parser': 7.26.5 - '@babel/types': 7.26.5 + '@babel/parser': 7.26.7 + '@babel/types': 7.26.7 source-map-js: 1.2.1 + make-dir@3.1.0: + dependencies: + semver: 6.3.1 + make-dir@4.0.0: dependencies: semver: 7.6.3 @@ -15619,6 +16361,10 @@ snapshots: dependencies: tmpl: 1.0.5 + manage-path@2.0.0: {} + + marky@1.2.5: {} + math-intrinsics@1.1.0: {} mdn-data@2.0.28: {} @@ -15627,17 +16373,13 @@ snapshots: media-query-parser@2.0.2: dependencies: - '@babel/runtime': 7.26.0 - - media-typer@0.3.0: {} - - merge-descriptors@1.0.3: {} + '@babel/runtime': 7.26.7 merge-stream@2.0.0: {} merge2@1.4.1: {} - methods@1.1.2: {} + metaviewport-parser@0.3.0: {} micromatch@4.0.8: dependencies: @@ -15650,8 +16392,6 @@ snapshots: dependencies: mime-db: 1.52.0 - mime@1.6.0: {} - mimic-fn@2.1.0: {} min-indent@1.0.1: {} @@ -15680,20 +16420,20 @@ snapshots: modern-ahocorasick@1.1.0: {} - motion-dom@11.16.4: + motion-dom@11.18.1: dependencies: - motion-utils: 11.16.0 + motion-utils: 11.18.1 - motion-utils@11.16.0: {} + motion-utils@11.18.1: {} - motion@11.18.0(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + motion@11.18.2(@emotion/is-prop-valid@1.3.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: - framer-motion: 11.18.0(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + framer-motion: 11.18.2(@emotion/is-prop-valid@1.3.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) tslib: 2.8.1 optionalDependencies: '@emotion/is-prop-valid': 1.3.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) mrmime@2.0.0: {} @@ -15701,7 +16441,7 @@ snapshots: ms@2.1.3: {} - msw@2.7.0(@types/node@20.13.0)(typescript@5.4.5): + msw@2.7.0(@types/node@20.13.0)(typescript@5.7.3): dependencies: '@bundled-es-modules/cookie': 2.0.1 '@bundled-es-modules/statuses': 1.0.1 @@ -15719,10 +16459,10 @@ snapshots: path-to-regexp: 6.3.0 picocolors: 1.1.1 strict-event-emitter: 0.5.1 - type-fest: 4.32.0 + type-fest: 4.33.0 yargs: 17.7.2 optionalDependencies: - typescript: 5.4.5 + typescript: 5.7.3 transitivePeerDependencies: - '@types/node' @@ -15742,34 +16482,42 @@ snapshots: natural-compare@1.4.0: {} - negotiator@0.6.3: {} + netmask@2.0.2: {} - next@15.1.4(@babel/core@7.26.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next@15.1.6(@babel/core@7.26.7)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: - '@next/env': 15.1.4 + '@next/env': 15.1.6 '@swc/counter': 0.1.3 '@swc/helpers': 0.5.15 busboy: 1.6.0 - caniuse-lite: 1.0.30001692 + caniuse-lite: 1.0.30001695 postcss: 8.4.31 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - styled-jsx: 5.1.6(@babel/core@7.26.0)(babel-plugin-macros@3.1.0)(react@18.3.1) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + styled-jsx: 5.1.6(@babel/core@7.26.7)(babel-plugin-macros@3.1.0)(react@19.0.0) optionalDependencies: - '@next/swc-darwin-arm64': 15.1.4 - '@next/swc-darwin-x64': 15.1.4 - '@next/swc-linux-arm64-gnu': 15.1.4 - '@next/swc-linux-arm64-musl': 15.1.4 - '@next/swc-linux-x64-gnu': 15.1.4 - '@next/swc-linux-x64-musl': 15.1.4 - '@next/swc-win32-arm64-msvc': 15.1.4 - '@next/swc-win32-x64-msvc': 15.1.4 - '@playwright/test': 1.49.1 + '@next/swc-darwin-arm64': 15.1.6 + '@next/swc-darwin-x64': 15.1.6 + '@next/swc-linux-arm64-gnu': 15.1.6 + '@next/swc-linux-arm64-musl': 15.1.6 + '@next/swc-linux-x64-gnu': 15.1.6 + '@next/swc-linux-x64-musl': 15.1.6 + '@next/swc-win32-arm64-msvc': 15.1.6 + '@next/swc-win32-x64-msvc': 15.1.6 + '@playwright/test': 1.50.0 sharp: 0.33.5 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros + nextjs-toploader@3.7.15(next@15.1.6(@babel/core@7.26.7)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + next: 15.1.6(@babel/core@7.26.7)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + nprogress: 0.2.0 + prop-types: 15.8.1 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + no-case@3.0.4: dependencies: lower-case: 2.0.2 @@ -15789,22 +16537,15 @@ snapshots: dependencies: path-key: 3.1.1 + nprogress@0.2.0: {} + nth-check@2.1.1: dependencies: boolbase: 1.0.0 - nuqs@2.3.0(next@15.1.4(@babel/core@7.26.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router-dom@7.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@7.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1): - dependencies: - mitt: 3.0.1 - react: 18.3.1 - optionalDependencies: - next: 15.1.4(@babel/core@7.26.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react-router: 7.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react-router-dom: 7.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - nwsapi@2.2.16: {} - nx@20.3.1(@swc/core@1.10.7): + nx@20.3.3(@swc/core@1.10.11): dependencies: '@napi-rs/wasm-runtime': 0.2.4 '@yarnpkg/lockfile': 1.1.0 @@ -15841,17 +16582,17 @@ snapshots: yargs: 17.7.2 yargs-parser: 21.1.1 optionalDependencies: - '@nx/nx-darwin-arm64': 20.3.1 - '@nx/nx-darwin-x64': 20.3.1 - '@nx/nx-freebsd-x64': 20.3.1 - '@nx/nx-linux-arm-gnueabihf': 20.3.1 - '@nx/nx-linux-arm64-gnu': 20.3.1 - '@nx/nx-linux-arm64-musl': 20.3.1 - '@nx/nx-linux-x64-gnu': 20.3.1 - '@nx/nx-linux-x64-musl': 20.3.1 - '@nx/nx-win32-arm64-msvc': 20.3.1 - '@nx/nx-win32-x64-msvc': 20.3.1 - '@swc/core': 1.10.7(@swc/helpers@0.5.15) + '@nx/nx-darwin-arm64': 20.3.3 + '@nx/nx-darwin-x64': 20.3.3 + '@nx/nx-freebsd-x64': 20.3.3 + '@nx/nx-linux-arm-gnueabihf': 20.3.3 + '@nx/nx-linux-arm64-gnu': 20.3.3 + '@nx/nx-linux-arm64-musl': 20.3.3 + '@nx/nx-linux-x64-gnu': 20.3.3 + '@nx/nx-linux-x64-musl': 20.3.3 + '@nx/nx-win32-arm64-msvc': 20.3.3 + '@nx/nx-win32-x64-msvc': 20.3.3 + '@swc/core': 1.10.11(@swc/helpers@0.5.15) transitivePeerDependencies: - debug @@ -15868,7 +16609,7 @@ snapshots: call-bind: 1.0.8 call-bound: 1.0.3 define-properties: 1.2.1 - es-object-atoms: 1.0.1 + es-object-atoms: 1.1.1 has-symbols: 1.1.0 object-keys: 1.1.1 @@ -15876,25 +16617,23 @@ snapshots: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-object-atoms: 1.0.1 + es-object-atoms: 1.1.1 object.fromentries@2.0.8: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 es-abstract: 1.23.9 - es-object-atoms: 1.0.1 + es-object-atoms: 1.1.1 object.values@1.2.1: dependencies: call-bind: 1.0.8 call-bound: 1.0.3 define-properties: 1.2.1 - es-object-atoms: 1.0.1 + es-object-atoms: 1.1.1 - on-finished@2.4.1: - dependencies: - ee-first: 1.1.1 + on-net-listen@1.1.2: {} once@1.4.0: dependencies: @@ -15910,6 +16649,8 @@ snapshots: is-docker: 2.2.1 is-wsl: 2.2.0 + opener@1.5.2: {} + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -15956,12 +16697,34 @@ snapshots: p-try@2.2.0: {} + pac-proxy-agent@7.1.0: + dependencies: + '@tootallnate/quickjs-emscripten': 0.23.0 + agent-base: 7.1.3 + debug: 4.4.0 + get-uri: 6.0.4 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + pac-resolver: 7.0.1 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + + pac-resolver@7.0.1: + dependencies: + degenerator: 5.0.1 + netmask: 2.0.2 + package-json-from-dist@1.0.1: {} + pako@1.0.11: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 + parse-cache-control@1.0.1: {} + parse-json@5.2.0: dependencies: '@babel/code-frame': 7.26.2 @@ -15973,8 +16736,6 @@ snapshots: dependencies: entities: 4.5.0 - parseurl@1.3.3: {} - path-exists@4.0.0: {} path-is-absolute@1.0.1: {} @@ -15988,8 +16749,6 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.2 - path-to-regexp@0.1.12: {} - path-to-regexp@6.3.0: {} path-type@4.0.0: {} @@ -15998,6 +16757,8 @@ snapshots: pathval@2.0.0: {} + pend@1.2.0: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -16012,38 +16773,38 @@ snapshots: dependencies: find-up: 4.1.0 - playwright-core@1.49.1: {} + playwright-core@1.50.0: {} - playwright@1.49.1: + playwright@1.50.0: dependencies: - playwright-core: 1.49.1 + playwright-core: 1.50.0 optionalDependencies: fsevents: 2.3.2 possible-typed-array-names@1.0.0: {} - postcss-import@15.1.0(postcss@8.5.0): + postcss-import@15.1.0(postcss@8.5.1): dependencies: - postcss: 8.5.0 + postcss: 8.5.1 postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.10 - postcss-js@4.0.1(postcss@8.5.0): + postcss-js@4.0.1(postcss@8.5.1): dependencies: camelcase-css: 2.0.1 - postcss: 8.5.0 + postcss: 8.5.1 - postcss-load-config@4.0.2(postcss@8.5.0): + postcss-load-config@4.0.2(postcss@8.5.1): dependencies: lilconfig: 3.1.3 yaml: 2.7.0 optionalDependencies: - postcss: 8.5.0 + postcss: 8.5.1 - postcss-nested@6.2.0(postcss@8.5.0): + postcss-nested@6.2.0(postcss@8.5.1): dependencies: - postcss: 8.5.0 + postcss: 8.5.1 postcss-selector-parser: 6.1.2 postcss-selector-parser@6.1.2: @@ -16059,7 +16820,7 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - postcss@8.5.0: + postcss@8.5.1: dependencies: nanoid: 3.3.8 picocolors: 1.1.1 @@ -16073,6 +16834,8 @@ snapshots: prettier@3.4.2: {} + pretty-bytes@5.6.0: {} + pretty-format@27.5.1: dependencies: ansi-regex: 5.0.1 @@ -16087,6 +16850,8 @@ snapshots: prismjs@1.29.0: {} + progress@2.0.3: {} + prompts@2.4.2: dependencies: kleur: 3.0.3 @@ -16098,10 +16863,18 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 - proxy-addr@2.0.7: + proxy-agent@6.5.0: dependencies: - forwarded: 0.2.0 - ipaddr.js: 1.9.1 + agent-base: 7.1.3 + debug: 4.4.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + lru-cache: 7.18.3 + pac-proxy-agent: 7.1.0 + proxy-from-env: 1.1.0 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color proxy-from-env@1.1.0: {} @@ -16109,61 +16882,83 @@ snapshots: dependencies: punycode: 2.3.1 + pump@3.0.2: + dependencies: + end-of-stream: 1.4.4 + once: 1.4.0 + punycode@2.3.1: {} - pure-rand@6.1.0: {} + puppeteer-core@23.11.1: + dependencies: + '@puppeteer/browsers': 2.6.1 + chromium-bidi: 0.11.0(devtools-protocol@0.0.1367902) + debug: 4.4.0 + devtools-protocol: 0.0.1367902 + typed-query-selector: 2.12.0 + ws: 8.18.0 + transitivePeerDependencies: + - bare-buffer + - bufferutil + - supports-color + - utf-8-validate - qs@6.13.0: + puppeteer-core@24.1.1: dependencies: - side-channel: 1.1.0 + '@puppeteer/browsers': 2.7.0 + chromium-bidi: 1.1.0(devtools-protocol@0.0.1380148) + debug: 4.4.0 + devtools-protocol: 0.0.1380148 + typed-query-selector: 2.12.0 + ws: 8.18.0 + transitivePeerDependencies: + - bare-buffer + - bufferutil + - supports-color + - utf-8-validate - query-string@9.1.1: + puppeteer@24.1.1(typescript@5.7.3): dependencies: - decode-uri-component: 0.4.1 - filter-obj: 5.1.0 - split-on-first: 3.0.0 + '@puppeteer/browsers': 2.7.0 + chromium-bidi: 1.1.0(devtools-protocol@0.0.1380148) + cosmiconfig: 9.0.0(typescript@5.7.3) + devtools-protocol: 0.0.1380148 + puppeteer-core: 24.1.1 + typed-query-selector: 2.12.0 + transitivePeerDependencies: + - bare-buffer + - bufferutil + - supports-color + - typescript + - utf-8-validate + + pure-rand@6.1.0: {} querystringify@2.2.0: {} queue-microtask@1.2.3: {} - range-parser@1.2.1: {} - - raw-body@2.5.2: - dependencies: - bytes: 3.1.2 - http-errors: 2.0.0 - iconv-lite: 0.4.24 - unpipe: 1.0.0 - - react-dom@18.3.1(react@18.3.1): + react-dom@19.0.0(react@19.0.0): dependencies: - loose-envify: 1.4.0 - react: 18.3.1 - scheduler: 0.23.2 + react: 19.0.0 + scheduler: 0.25.0 - react-error-boundary@3.1.4(react@18.3.1): + react-error-boundary@3.1.4(react@19.0.0): dependencies: - '@babel/runtime': 7.26.0 - react: 18.3.1 + '@babel/runtime': 7.26.7 + react: 19.0.0 react-fast-compare@3.2.2: {} - react-hook-form@7.54.2(react@18.3.1): - dependencies: - react: 18.3.1 - - react-intersection-observer@9.14.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + react-hook-form@7.54.2(react@19.0.0): dependencies: - react: 18.3.1 - optionalDependencies: - react-dom: 18.3.1(react@18.3.1) + react: 19.0.0 - react-intersection-observer@9.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + react-intersection-observer@9.15.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: - react: 18.3.1 + react: 19.0.0 optionalDependencies: - react-dom: 18.3.1(react@18.3.1) + react-dom: 19.0.0(react@19.0.0) react-is@16.13.1: {} @@ -16171,73 +16966,59 @@ snapshots: react-is@18.3.1: {} - react-lazy-load-image-component@1.6.3(react@18.3.1): + react-lazy-load-image-component@1.6.3(react@19.0.0): dependencies: lodash.debounce: 4.0.8 lodash.throttle: 4.1.1 - react: 18.3.1 + react: 19.0.0 - react-loading-skeleton@3.5.0(react@18.3.1): + react-loading-skeleton@3.5.0(react@19.0.0): dependencies: - react: 18.3.1 + react: 19.0.0 - react-remove-scroll-bar@2.3.8(@types/react@18.3.18)(react@18.3.1): + react-remove-scroll-bar@2.3.8(@types/react@19.0.8)(react@19.0.0): dependencies: - react: 18.3.1 - react-style-singleton: 2.2.3(@types/react@18.3.18)(react@18.3.1) + react: 19.0.0 + react-style-singleton: 2.2.3(@types/react@19.0.8)(react@19.0.0) tslib: 2.8.1 optionalDependencies: - '@types/react': 18.3.18 + '@types/react': 19.0.8 - react-remove-scroll@2.6.2(@types/react@18.3.18)(react@18.3.1): + react-remove-scroll@2.6.3(@types/react@19.0.8)(react@19.0.0): dependencies: - react: 18.3.1 - react-remove-scroll-bar: 2.3.8(@types/react@18.3.18)(react@18.3.1) - react-style-singleton: 2.2.3(@types/react@18.3.18)(react@18.3.1) + react: 19.0.0 + react-remove-scroll-bar: 2.3.8(@types/react@19.0.8)(react@19.0.0) + react-style-singleton: 2.2.3(@types/react@19.0.8)(react@19.0.0) tslib: 2.8.1 - use-callback-ref: 1.3.3(@types/react@18.3.18)(react@18.3.1) - use-sidecar: 1.1.3(@types/react@18.3.18)(react@18.3.1) + use-callback-ref: 1.3.3(@types/react@19.0.8)(react@19.0.0) + use-sidecar: 1.1.3(@types/react@19.0.8)(react@19.0.0) optionalDependencies: - '@types/react': 18.3.18 + '@types/react': 19.0.8 - react-router-dom@7.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-router: 7.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - optional: true - - react-router@7.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@types/cookie': 0.6.0 - cookie: 1.0.2 - react: 18.3.1 - set-cookie-parser: 2.7.1 - turbo-stream: 2.4.0 - optionalDependencies: - react-dom: 18.3.1(react@18.3.1) - optional: true - - react-style-singleton@2.2.3(@types/react@18.3.18)(react@18.3.1): + react-style-singleton@2.2.3(@types/react@19.0.8)(react@19.0.0): dependencies: get-nonce: 1.0.1 - react: 18.3.1 + react: 19.0.0 tslib: 2.8.1 optionalDependencies: - '@types/react': 18.3.18 + '@types/react': 19.0.8 - react-swipeable@7.0.2(react@18.3.1): + react-swipeable@7.0.2(react@19.0.0): dependencies: - react: 18.3.1 + react: 19.0.0 - react-zoom-pan-pinch@3.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + react-use-measure@2.1.7(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.0.0 + optionalDependencies: + react-dom: 19.0.0(react@19.0.0) - react@18.3.1: + react-zoom-pan-pinch@3.6.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: - loose-envify: 1.4.0 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + react@19.0.0: {} read-cache@1.0.0: dependencies: @@ -16264,7 +17045,7 @@ snapshots: define-properties: 1.2.1 es-abstract: 1.23.9 es-errors: 1.3.0 - es-object-atoms: 1.0.1 + es-object-atoms: 1.1.1 get-intrinsic: 1.2.7 get-proto: 1.0.1 which-builtin-type: 1.2.1 @@ -16279,7 +17060,7 @@ snapshots: regenerator-transform@0.15.2: dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.26.7 regexp.prototype.flags@1.5.4: dependencies: @@ -16305,6 +17086,8 @@ snapshots: dependencies: jsesc: 3.0.2 + reinterval@1.1.0: {} + require-directory@2.1.1: {} requires-port@1.0.0: {} @@ -16336,35 +17119,48 @@ snapshots: onetime: 5.1.2 signal-exit: 3.0.7 + retimer@3.0.0: {} + reusify@1.0.4: {} rimraf@3.0.2: dependencies: glob: 7.2.3 - rollup@4.30.1: + robots-parser@3.0.1: {} + + rollup-plugin-visualizer@5.14.0(rollup@4.32.0): + dependencies: + open: 8.4.2 + picomatch: 4.0.2 + source-map: 0.7.4 + yargs: 17.7.2 + optionalDependencies: + rollup: 4.32.0 + + rollup@4.32.0: dependencies: '@types/estree': 1.0.6 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.30.1 - '@rollup/rollup-android-arm64': 4.30.1 - '@rollup/rollup-darwin-arm64': 4.30.1 - '@rollup/rollup-darwin-x64': 4.30.1 - '@rollup/rollup-freebsd-arm64': 4.30.1 - '@rollup/rollup-freebsd-x64': 4.30.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.30.1 - '@rollup/rollup-linux-arm-musleabihf': 4.30.1 - '@rollup/rollup-linux-arm64-gnu': 4.30.1 - '@rollup/rollup-linux-arm64-musl': 4.30.1 - '@rollup/rollup-linux-loongarch64-gnu': 4.30.1 - '@rollup/rollup-linux-powerpc64le-gnu': 4.30.1 - '@rollup/rollup-linux-riscv64-gnu': 4.30.1 - '@rollup/rollup-linux-s390x-gnu': 4.30.1 - '@rollup/rollup-linux-x64-gnu': 4.30.1 - '@rollup/rollup-linux-x64-musl': 4.30.1 - '@rollup/rollup-win32-arm64-msvc': 4.30.1 - '@rollup/rollup-win32-ia32-msvc': 4.30.1 - '@rollup/rollup-win32-x64-msvc': 4.30.1 + '@rollup/rollup-android-arm-eabi': 4.32.0 + '@rollup/rollup-android-arm64': 4.32.0 + '@rollup/rollup-darwin-arm64': 4.32.0 + '@rollup/rollup-darwin-x64': 4.32.0 + '@rollup/rollup-freebsd-arm64': 4.32.0 + '@rollup/rollup-freebsd-x64': 4.32.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.32.0 + '@rollup/rollup-linux-arm-musleabihf': 4.32.0 + '@rollup/rollup-linux-arm64-gnu': 4.32.0 + '@rollup/rollup-linux-arm64-musl': 4.32.0 + '@rollup/rollup-linux-loongarch64-gnu': 4.32.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.32.0 + '@rollup/rollup-linux-riscv64-gnu': 4.32.0 + '@rollup/rollup-linux-s390x-gnu': 4.32.0 + '@rollup/rollup-linux-x64-gnu': 4.32.0 + '@rollup/rollup-linux-x64-musl': 4.32.0 + '@rollup/rollup-win32-arm64-msvc': 4.32.0 + '@rollup/rollup-win32-ia32-msvc': 4.32.0 + '@rollup/rollup-win32-x64-msvc': 4.32.0 fsevents: 2.3.3 rrweb-cssom@0.8.0: {} @@ -16404,44 +17200,14 @@ snapshots: dependencies: xmlchars: 2.2.0 - scheduler@0.23.2: - dependencies: - loose-envify: 1.4.0 + scheduler@0.25.0: {} + + semver@5.7.2: {} semver@6.3.1: {} semver@7.6.3: {} - send@0.19.0: - dependencies: - debug: 2.6.9 - depd: 2.0.0 - destroy: 1.2.0 - encodeurl: 1.0.2 - escape-html: 1.0.3 - etag: 1.8.1 - fresh: 0.5.2 - http-errors: 2.0.0 - mime: 1.6.0 - ms: 2.1.3 - on-finished: 2.4.1 - range-parser: 1.2.1 - statuses: 2.0.1 - transitivePeerDependencies: - - supports-color - - serve-static@1.16.2: - dependencies: - encodeurl: 2.0.0 - escape-html: 1.0.3 - parseurl: 1.3.3 - send: 0.19.0 - transitivePeerDependencies: - - supports-color - - set-cookie-parser@2.7.1: - optional: true - set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -16462,9 +17228,7 @@ snapshots: dependencies: dunder-proto: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.0.1 - - setprototypeof@1.2.0: {} + es-object-atoms: 1.1.1 sharp@0.33.5: dependencies: @@ -16491,7 +17255,6 @@ snapshots: '@img/sharp-wasm32': 0.33.5 '@img/sharp-win32-ia32': 0.33.5 '@img/sharp-win32-x64': 0.33.5 - optional: true shebang-command@2.0.0: dependencies: @@ -16536,7 +17299,12 @@ snapshots: simple-swizzle@0.2.2: dependencies: is-arrayish: 0.3.2 - optional: true + + sirv@2.0.4: + dependencies: + '@polka/url': 1.0.0-next.28 + mrmime: 2.0.0 + totalist: 3.0.1 sirv@3.0.0: dependencies: @@ -16548,32 +17316,49 @@ snapshots: slash@3.0.0: {} + smart-buffer@4.2.0: {} + snake-case@3.0.4: dependencies: dot-case: 3.0.4 tslib: 2.8.1 - source-map-js@1.2.1: {} + socks-proxy-agent@8.0.5: + dependencies: + agent-base: 7.1.3 + debug: 4.4.0 + socks: 2.8.3 + transitivePeerDependencies: + - supports-color - source-map-support@0.5.13: + socks@2.8.3: dependencies: - buffer-from: 1.1.2 - source-map: 0.6.1 + ip-address: 9.0.5 + smart-buffer: 4.2.0 + + source-map-js@1.2.1: {} - source-map-support@0.5.21: + source-map-support@0.5.13: dependencies: buffer-from: 1.1.2 source-map: 0.6.1 - optional: true source-map@0.5.7: {} source-map@0.6.1: {} - split-on-first@3.0.0: {} + source-map@0.7.4: {} + + speedline-core@1.4.3: + dependencies: + '@types/node': 20.13.0 + image-ssim: 0.2.0 + jpeg-js: 0.4.4 sprintf-js@1.0.3: {} + sprintf-js@1.1.3: {} + stack-utils@2.0.6: dependencies: escape-string-regexp: 2.0.0 @@ -16586,6 +17371,13 @@ snapshots: streamsearch@1.1.0: {} + streamx@2.22.0: + dependencies: + fast-fifo: 1.3.2 + text-decoder: 1.2.3 + optionalDependencies: + bare-events: 2.5.4 + strict-event-emitter@0.5.1: {} string-length@4.0.2: @@ -16612,7 +17404,7 @@ snapshots: define-properties: 1.2.1 es-abstract: 1.23.9 es-errors: 1.3.0 - es-object-atoms: 1.0.1 + es-object-atoms: 1.1.1 get-intrinsic: 1.2.7 gopd: 1.2.0 has-symbols: 1.1.0 @@ -16633,7 +17425,7 @@ snapshots: define-data-property: 1.1.4 define-properties: 1.2.1 es-abstract: 1.23.9 - es-object-atoms: 1.0.1 + es-object-atoms: 1.1.1 has-property-descriptors: 1.0.2 string.prototype.trimend@1.0.9: @@ -16641,13 +17433,13 @@ snapshots: call-bind: 1.0.8 call-bound: 1.0.3 define-properties: 1.2.1 - es-object-atoms: 1.0.1 + es-object-atoms: 1.1.1 string.prototype.trimstart@1.0.8: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-object-atoms: 1.0.1 + es-object-atoms: 1.1.1 string_decoder@1.3.0: dependencies: @@ -16673,12 +17465,12 @@ snapshots: strip-json-comments@3.1.1: {} - styled-jsx@5.1.6(@babel/core@7.26.0)(babel-plugin-macros@3.1.0)(react@18.3.1): + styled-jsx@5.1.6(@babel/core@7.26.7)(babel-plugin-macros@3.1.0)(react@19.0.0): dependencies: client-only: 0.0.1 - react: 18.3.1 + react: 19.0.0 optionalDependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 babel-plugin-macros: 3.1.0 stylis@4.2.0: {} @@ -16726,10 +17518,6 @@ snapshots: tailwind-merge@2.6.0: {} - tailwindcss-animate@1.0.7(tailwindcss@3.4.17): - dependencies: - tailwindcss: 3.4.17 - tailwindcss@3.4.17: dependencies: '@alloc/quick-lru': 5.2.0 @@ -16746,17 +17534,27 @@ snapshots: normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.1.1 - postcss: 8.5.0 - postcss-import: 15.1.0(postcss@8.5.0) - postcss-js: 4.0.1(postcss@8.5.0) - postcss-load-config: 4.0.2(postcss@8.5.0) - postcss-nested: 6.2.0(postcss@8.5.0) + postcss: 8.5.1 + postcss-import: 15.1.0(postcss@8.5.1) + postcss-js: 4.0.1(postcss@8.5.1) + postcss-load-config: 4.0.2(postcss@8.5.1) + postcss-nested: 6.2.0(postcss@8.5.1) postcss-selector-parser: 6.1.2 resolve: 1.22.10 sucrase: 3.35.0 transitivePeerDependencies: - ts-node + tar-fs@3.0.8: + dependencies: + pump: 3.0.2 + tar-stream: 3.1.7 + optionalDependencies: + bare-fs: 4.0.1 + bare-path: 3.0.0 + transitivePeerDependencies: + - bare-buffer + tar-stream@2.2.0: dependencies: bl: 4.1.0 @@ -16765,13 +17563,11 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 - terser@5.37.0: + tar-stream@3.1.7: dependencies: - '@jridgewell/source-map': 0.3.6 - acorn: 8.14.0 - commander: 2.20.3 - source-map-support: 0.5.21 - optional: true + b4a: 1.6.7 + fast-fifo: 1.3.2 + streamx: 2.22.0 test-exclude@6.0.0: dependencies: @@ -16785,6 +17581,10 @@ snapshots: glob: 10.4.5 minimatch: 9.0.5 + text-decoder@1.2.3: + dependencies: + b4a: 1.6.7 + text-table@0.2.0: {} thenify-all@1.6.0: @@ -16795,13 +17595,21 @@ snapshots: dependencies: any-promise: 1.3.0 + third-party-web@0.26.2: {} + + third-party-web@0.26.6: {} + + through@2.3.8: {} + + timestring@6.0.0: {} + tinybench@2.9.0: {} tinyexec@0.3.2: {} tinyglobby@0.2.10: dependencies: - fdir: 6.4.2(picomatch@4.0.2) + fdir: 6.4.3(picomatch@4.0.2) picomatch: 4.0.2 tinypool@1.0.2: {} @@ -16810,11 +17618,17 @@ snapshots: tinyspy@3.0.2: {} - tldts-core@6.1.71: {} + tldts-core@6.1.75: {} + + tldts-core@6.1.76: {} - tldts@6.1.71: + tldts-icann@6.1.76: dependencies: - tldts-core: 6.1.71 + tldts-core: 6.1.76 + + tldts@6.1.75: + dependencies: + tldts-core: 6.1.75 tmp@0.2.3: {} @@ -16826,8 +17640,6 @@ snapshots: dependencies: is-number: 7.0.0 - toidentifier@1.0.1: {} - totalist@3.0.1: {} tough-cookie@4.1.4: @@ -16839,7 +17651,7 @@ snapshots: tough-cookie@5.1.0: dependencies: - tldts: 6.1.71 + tldts: 6.1.75 tr46@3.0.0: dependencies: @@ -16849,13 +17661,13 @@ snapshots: dependencies: punycode: 2.3.1 - ts-api-utils@1.4.3(typescript@5.4.5): + ts-api-utils@1.4.3(typescript@5.7.3): dependencies: - typescript: 5.4.5 + typescript: 5.7.3 ts-interface-checker@0.1.13: {} - ts-jest@29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@20.13.0)(babel-plugin-macros@3.1.0))(typescript@5.4.5): + ts-jest@29.2.5(@babel/core@7.26.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.7))(jest@29.7.0(@types/node@20.13.0)(babel-plugin-macros@3.1.0))(typescript@5.7.3): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 @@ -16866,13 +17678,13 @@ snapshots: lodash.memoize: 4.1.2 make-error: 1.3.6 semver: 7.6.3 - typescript: 5.4.5 + typescript: 5.7.3 yargs-parser: 21.1.1 optionalDependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.7 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.26.0) + babel-jest: 29.7.0(@babel/core@7.26.7) tsconfig-paths@4.2.0: dependencies: @@ -16882,9 +17694,6 @@ snapshots: tslib@2.8.1: {} - turbo-stream@2.4.0: - optional: true - type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -16895,12 +17704,7 @@ snapshots: type-fest@0.21.3: {} - type-fest@4.32.0: {} - - type-is@1.6.18: - dependencies: - media-typer: 0.3.0 - mime-types: 2.1.35 + type-fest@4.33.0: {} typed-array-buffer@1.0.3: dependencies: @@ -16911,7 +17715,7 @@ snapshots: typed-array-byte-length@1.0.3: dependencies: call-bind: 1.0.8 - for-each: 0.3.3 + for-each: 0.3.4 gopd: 1.2.0 has-proto: 1.2.0 is-typed-array: 1.1.15 @@ -16920,7 +17724,7 @@ snapshots: dependencies: available-typed-arrays: 1.0.7 call-bind: 1.0.8 - for-each: 0.3.3 + for-each: 0.3.4 gopd: 1.2.0 has-proto: 1.2.0 is-typed-array: 1.1.15 @@ -16929,13 +17733,19 @@ snapshots: typed-array-length@1.0.7: dependencies: call-bind: 1.0.8 - for-each: 0.3.3 + for-each: 0.3.4 gopd: 1.2.0 is-typed-array: 1.1.15 possible-typed-array-names: 1.0.0 reflect.getprototypeof: 1.0.10 - typescript@5.4.5: {} + typed-query-selector@2.12.0: {} + + typedarray-to-buffer@3.1.5: + dependencies: + is-typedarray: 1.0.0 + + typescript@5.7.3: {} unbox-primitive@1.1.0: dependencies: @@ -16944,6 +17754,11 @@ snapshots: has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 + unbzip2-stream@1.4.3: + dependencies: + buffer: 5.7.1 + through: 2.3.8 + undici-types@5.26.5: {} unicode-canonical-property-names-ecmascript@2.0.1: {} @@ -16957,9 +17772,11 @@ snapshots: unicode-property-aliases-ecmascript@2.1.0: {} - universalify@0.2.0: {} + unique-string@2.0.0: + dependencies: + crypto-random-string: 2.0.0 - unpipe@1.0.0: {} + universalify@0.2.0: {} update-browserslist-db@1.1.2(browserslist@4.24.4): dependencies: @@ -16978,29 +17795,28 @@ snapshots: url-pattern@1.0.3: {} - use-callback-ref@1.3.3(@types/react@18.3.18)(react@18.3.1): + use-callback-ref@1.3.3(@types/react@19.0.8)(react@19.0.0): dependencies: - react: 18.3.1 + react: 19.0.0 tslib: 2.8.1 optionalDependencies: - '@types/react': 18.3.18 + '@types/react': 19.0.8 - use-sidecar@1.1.3(@types/react@18.3.18)(react@18.3.1): + use-sidecar@1.1.3(@types/react@19.0.8)(react@19.0.0): dependencies: detect-node-es: 1.1.0 - react: 18.3.1 + react: 19.0.0 tslib: 2.8.1 optionalDependencies: - '@types/react': 18.3.18 - - use-sync-external-store@1.4.0(react@18.3.1): - dependencies: - react: 18.3.1 - optional: true + '@types/react': 19.0.8 util-deprecate@1.0.2: {} - utils-merge@1.0.1: {} + uuid-parse@1.1.0: {} + + uuid@11.1.0: {} + + uuid@8.3.2: {} v8-to-istanbul@9.3.0: dependencies: @@ -17008,24 +17824,24 @@ snapshots: '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 - vary@1.1.2: {} - - vaul@1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + vaul@1.1.2(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: - '@radix-ui/react-dialog': 1.1.4(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@radix-ui/react-dialog': 1.1.5(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) transitivePeerDependencies: - '@types/react' - '@types/react-dom' - vite-node@2.1.8(@types/node@20.13.0)(terser@5.37.0): + vite-bundle-analyzer@0.16.1: {} + + vite-node@2.1.8(@types/node@20.13.0): dependencies: cac: 6.7.14 debug: 4.4.0 es-module-lexer: 1.6.0 pathe: 1.1.2 - vite: 5.4.11(@types/node@20.13.0)(terser@5.37.0) + vite: 5.4.14(@types/node@20.13.0) transitivePeerDependencies: - '@types/node' - less @@ -17037,43 +17853,47 @@ snapshots: - supports-color - terser - vite-plugin-svgr@4.3.0(rollup@4.30.1)(typescript@5.4.5)(vite@6.0.7(@types/node@20.13.0)(jiti@1.21.7)(terser@5.37.0)(yaml@2.7.0)): + vite-plugin-image-optimizer@1.1.8(vite@6.0.11(@types/node@20.13.0)(jiti@1.21.7)(yaml@2.7.0)): dependencies: - '@rollup/pluginutils': 5.1.4(rollup@4.30.1) - '@svgr/core': 8.1.0(typescript@5.4.5) - '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.4.5)) - vite: 6.0.7(@types/node@20.13.0)(jiti@1.21.7)(terser@5.37.0)(yaml@2.7.0) + ansi-colors: 4.1.3 + pathe: 1.1.2 + vite: 6.0.11(@types/node@20.13.0)(jiti@1.21.7)(yaml@2.7.0) + + vite-plugin-svgr@4.3.0(rollup@4.32.0)(typescript@5.7.3)(vite@6.0.11(@types/node@20.13.0)(jiti@1.21.7)(yaml@2.7.0)): + dependencies: + '@rollup/pluginutils': 5.1.4(rollup@4.32.0) + '@svgr/core': 8.1.0(typescript@5.7.3) + '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.7.3)) + vite: 6.0.11(@types/node@20.13.0)(jiti@1.21.7)(yaml@2.7.0) transitivePeerDependencies: - rollup - supports-color - typescript - vite@5.4.11(@types/node@20.13.0)(terser@5.37.0): + vite@5.4.14(@types/node@20.13.0): dependencies: esbuild: 0.21.5 - postcss: 8.5.0 - rollup: 4.30.1 + postcss: 8.5.1 + rollup: 4.32.0 optionalDependencies: '@types/node': 20.13.0 fsevents: 2.3.3 - terser: 5.37.0 - vite@6.0.7(@types/node@20.13.0)(jiti@1.21.7)(terser@5.37.0)(yaml@2.7.0): + vite@6.0.11(@types/node@20.13.0)(jiti@1.21.7)(yaml@2.7.0): dependencies: esbuild: 0.24.2 - postcss: 8.5.0 - rollup: 4.30.1 + postcss: 8.5.1 + rollup: 4.32.0 optionalDependencies: '@types/node': 20.13.0 fsevents: 2.3.3 jiti: 1.21.7 - terser: 5.37.0 yaml: 2.7.0 - vitest@2.1.8(@types/node@20.13.0)(@vitest/ui@2.1.8)(happy-dom@12.10.3)(jsdom@26.0.0)(msw@2.7.0(@types/node@20.13.0)(typescript@5.4.5))(terser@5.37.0): + vitest@2.1.8(@types/node@20.13.0)(@vitest/ui@2.1.8)(jsdom@26.0.0)(msw@2.7.0(@types/node@20.13.0)(typescript@5.7.3)): dependencies: '@vitest/expect': 2.1.8 - '@vitest/mocker': 2.1.8(msw@2.7.0(@types/node@20.13.0)(typescript@5.4.5))(vite@5.4.11(@types/node@20.13.0)(terser@5.37.0)) + '@vitest/mocker': 2.1.8(msw@2.7.0(@types/node@20.13.0)(typescript@5.7.3))(vite@5.4.14(@types/node@20.13.0)) '@vitest/pretty-format': 2.1.8 '@vitest/runner': 2.1.8 '@vitest/snapshot': 2.1.8 @@ -17089,13 +17909,12 @@ snapshots: tinyexec: 0.3.2 tinypool: 1.0.2 tinyrainbow: 1.2.0 - vite: 5.4.11(@types/node@20.13.0)(terser@5.37.0) - vite-node: 2.1.8(@types/node@20.13.0)(terser@5.37.0) + vite: 5.4.14(@types/node@20.13.0) + vite-node: 2.1.8(@types/node@20.13.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 20.13.0 '@vitest/ui': 2.1.8(vitest@2.1.8) - happy-dom: 12.10.3 jsdom: 26.0.0 transitivePeerDependencies: - less @@ -17126,6 +17945,25 @@ snapshots: webidl-conversions@7.0.0: {} + webpack-bundle-analyzer@4.10.1: + dependencies: + '@discoveryjs/json-ext': 0.5.7 + acorn: 8.14.0 + acorn-walk: 8.3.4 + commander: 7.2.0 + debounce: 1.2.1 + escape-string-regexp: 4.0.0 + gzip-size: 6.0.0 + html-escaper: 2.0.2 + is-plain-object: 5.0.0 + opener: 1.5.2 + picocolors: 1.1.1 + sirv: 2.0.4 + ws: 7.5.10 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + whatwg-encoding@2.0.0: dependencies: iconv-lite: 0.6.3 @@ -17161,7 +17999,7 @@ snapshots: call-bound: 1.0.3 function.prototype.name: 1.1.8 has-tostringtag: 1.0.2 - is-async-function: 2.1.0 + is-async-function: 2.1.1 is-date-object: 1.1.0 is-finalizationregistry: 1.1.1 is-generator-function: 1.1.0 @@ -17184,7 +18022,7 @@ snapshots: available-typed-arrays: 1.0.7 call-bind: 1.0.8 call-bound: 1.0.3 - for-each: 0.3.3 + for-each: 0.3.4 gopd: 1.2.0 has-tostringtag: 1.0.2 @@ -17219,13 +18057,24 @@ snapshots: wrappy@1.0.2: {} + write-file-atomic@3.0.3: + dependencies: + imurmurhash: 0.1.4 + is-typedarray: 1.0.0 + signal-exit: 3.0.7 + typedarray-to-buffer: 3.1.5 + write-file-atomic@4.0.2: dependencies: imurmurhash: 0.1.4 signal-exit: 3.0.7 + ws@7.5.10: {} + ws@8.18.0: {} + xdg-basedir@4.0.0: {} + xml-name-validator@4.0.0: {} xml-name-validator@5.0.0: {} @@ -17252,7 +18101,12 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 - yjs@13.6.22: + yauzl@2.10.0: + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + + yjs@13.6.23: dependencies: lib0: 0.2.99 @@ -17260,11 +18114,11 @@ snapshots: yoctocolors-cjs@2.1.2: {} + zod@3.23.8: {} + zod@3.24.1: {} - zustand@5.0.3(@types/react@18.3.18)(immer@9.0.21)(react@18.3.1)(use-sync-external-store@1.4.0(react@18.3.1)): + zustand@5.0.3(@types/react@19.0.8)(react@19.0.0): optionalDependencies: - '@types/react': 18.3.18 - immer: 9.0.21 - react: 18.3.1 - use-sync-external-store: 1.4.0(react@18.3.1) + '@types/react': 19.0.8 + react: 19.0.0 diff --git a/services/ahhachul.com/.gitignore b/services/ahhachul.com/.gitignore index b88c8135a..df0781a1b 100644 --- a/services/ahhachul.com/.gitignore +++ b/services/ahhachul.com/.gitignore @@ -23,6 +23,6 @@ dist-ssr *.sln *.sw? /test-results/ -/playwright-report/ +e2e/playwright-report/ /blob-report/ /playwright/.cache/ diff --git a/services/ahhachul.com/index.html b/services/ahhachul.com/index.html index e25318e76..18c5bf195 100644 --- a/services/ahhachul.com/index.html +++ b/services/ahhachul.com/index.html @@ -3,6 +3,7 @@ + + + + +
+
+
+
+
diff --git a/services/ahhachul.com/package.json b/services/ahhachul.com/package.json index 3873a0871..a64036b5e 100644 --- a/services/ahhachul.com/package.json +++ b/services/ahhachul.com/package.json @@ -8,6 +8,7 @@ "dev:mock": "vite --mode mock", "build": "tsc -b && vite build", "preview": "vite preview", + "type-check": "tsc --noEmit", "lint:es": "eslint src", "lint:es:fix": "eslint src --fix", "lint:etc": "prettier src --check", @@ -21,15 +22,23 @@ "test:playwright": "playwright test", "test:playwright:ui": "playwright test --ui", "test:playwright:debug": "playwright test --debug", - "test:all": "vitest run && playwright test" + "test:all": "vitest run && playwright test", + "security-audit": "pnpm audit", + "security-audit:fix": "pnpm audit --fix", + "analyze": "ANALYZE=true vite build", + "test:perf": "node scripts/performance-test.js", + "test:perf:load": "autocannon http://localhost:3000", + "test:perf:lighthouse": "lighthouse http://localhost:3000 --view" }, "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", "@hookform/resolvers": "^3.9.1", + "@lexical/link": "^0.27.1", "@lexical/react": "^0.23.1", "@lottiefiles/react-lottie-player": "^3.6.0", "@radix-ui/react-dropdown-menu": "^2.1.4", + "@radix-ui/react-icons": "^1.3.2", "@stackflow/core": "^1.0.10", "@stackflow/link": "^1.4.0", "@stackflow/plugin-basic-ui": "^1.5.3", @@ -41,41 +50,51 @@ "axios": "^1.7.9", "js-cookie": "^3.0.5", "lexical": "^0.23.1", + "lucide-react": "^0.460.0", "motion": "^11.18.0", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", "react-hook-form": "^7.49.3", "react-intersection-observer": "^9.15.0", "react-lazy-load-image-component": "^1.6.3", "react-loading-skeleton": "^3.5.0", "react-swipeable": "^7.0.2", + "react-use-measure": "^2.1.7", "react-zoom-pan-pinch": "3.6.0", "rxjs": "^7.8.1", "swiper": "^11.1.15", + "uuid": "^11.1.0", "vaul": "^1.1.2", - "zod": "^3.24.1", "zustand": "^5.0.2" }, "devDependencies": { "@ahhachul/utils": "workspace:*", "@playwright/test": "^1.49.1", + "@svgr/plugin-svgo": "^8.1.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.1.0", "@testing-library/react-hooks": "^8.0.1", "@testing-library/user-event": "^14.5.2", "@types/js-cookie": "^3.0.6", - "@types/node": "20.13.0", - "@types/react": "^18.3.12", - "@types/react-dom": "^18.3.1", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", "@types/react-lazy-load-image-component": "^1.6.4", "@vitejs/plugin-react-swc": "^3.7.2", "@vitest/coverage-v8": "^2.1.8", "@vitest/ui": "^2.1.8", + "autocannon": "^8.0.0", "globals": "^15.12.0", "jsdom": "^26.0.0", + "lighthouse": "^12.3.0", "msw": "^2.7.0", - "typescript": "5.4.5", + "puppeteer": "^24.1.1", + "rollup-plugin-visualizer": "^5.14.0", + "sharp": "^0.33.5", + "typescript": "^5", "vite": "^6.0.1", + "vite-bundle-analyzer": "^0.16.1", + "vite-plugin-image-optimizer": "^1.1.8", "vite-plugin-svgr": "^4.3.0", "vitest": "^2.1.8" }, diff --git a/services/ahhachul.com/playwright.config.ts b/services/ahhachul.com/playwright.config.ts index 44d543d99..7d9c14e57 100644 --- a/services/ahhachul.com/playwright.config.ts +++ b/services/ahhachul.com/playwright.config.ts @@ -1,4 +1,4 @@ -import { defineConfig, devices } from '@playwright/test' +import { defineConfig, devices } from '@playwright/test'; /** * Read environment variables from file. @@ -12,7 +12,7 @@ import { defineConfig, devices } from '@playwright/test' * See https://playwright.dev/docs/test-configuration. */ export default defineConfig({ - testDir: './__tests__', + testDir: './src/__tests__', /* Run tests in files in parallel */ fullyParallel: true, /* Fail the build on CI if you accidentally left test.only in the source code. */ @@ -22,7 +22,9 @@ export default defineConfig({ /* Opt out of parallel tests on CI. */ workers: process.env.CI ? 1 : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: 'html', + reporter: [['html'], ['list']], + outputDir: 'e2e/playwright-report', + // CI ํ™˜๊ฒฝ ์„ค์ • /* ํ…Œ์ŠคํŠธ์— ํฌํ•จ์‹œํ‚ฌ ํŒŒ์ผ ํ˜•์‹ */ testMatch: '**/*.spec.ts', /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ @@ -32,6 +34,8 @@ export default defineConfig({ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', + + screenshot: 'only-on-failure', }, /* Configure projects for major browsers */ @@ -78,4 +82,4 @@ export default defineConfig({ // url: 'http://127.0.0.1:3000', // reuseExistingServer: !process.env.CI, // }, -}) +}); diff --git a/services/ahhachul.com/src/assets/fonts/pretendard-variable.woff2 b/services/ahhachul.com/public/fonts/pretendard-variable.woff2 similarity index 100% rename from services/ahhachul.com/src/assets/fonts/pretendard-variable.woff2 rename to services/ahhachul.com/public/fonts/pretendard-variable.woff2 diff --git a/services/ahhachul.com/public/images/lost112.png b/services/ahhachul.com/public/images/lost112.png new file mode 100644 index 000000000..da02e0132 Binary files /dev/null and b/services/ahhachul.com/public/images/lost112.png differ diff --git a/services/ahhachul.com/public/initial-loader.css b/services/ahhachul.com/public/initial-loader.css new file mode 100644 index 000000000..56cafcc41 --- /dev/null +++ b/services/ahhachul.com/public/initial-loader.css @@ -0,0 +1,52 @@ +#initial-loader { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + width: 100%; + position: fixed; + top: 0; + left: 0; + background-color: white; + z-index: 9999; +} + +.ios-spinner { + position: relative; + top: -48px; + width: 13px; + height: 13px; +} + +.ios-spinner::after { + content: ''; + display: block; + width: 100%; + height: 100%; + border-radius: 50%; + box-sizing: border-box; +} + +.ios-spinner::before { + content: ''; + display: block; + position: absolute; + top: -3px; + left: -3px; + right: -3px; + bottom: -3px; + border-radius: 50%; + box-sizing: border-box; + border: 2px solid rgba(200, 200, 200, 0.2); + border-top-color: #004fec; + animation: ios-spin 1.5s ease infinite; +} + +@keyframes ios-spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/services/ahhachul.com/public/lost112.png b/services/ahhachul.com/public/lost112.png new file mode 100644 index 000000000..da02e0132 Binary files /dev/null and b/services/ahhachul.com/public/lost112.png differ diff --git a/services/ahhachul.com/scripts/performance-test.js b/services/ahhachul.com/scripts/performance-test.js new file mode 100644 index 000000000..97b7944ab --- /dev/null +++ b/services/ahhachul.com/scripts/performance-test.js @@ -0,0 +1,31 @@ +import autocannon from 'autocannon'; +import lighthouse from 'lighthouse'; +import puppeteer from 'puppeteer'; + +async function runPerformanceTests() { + // Lighthouse ํ…Œ์ŠคํŠธ + const browser = await puppeteer.launch({ headless: true }); + const results = await lighthouse('http://localhost:3000', { + port: new URL(browser.wsEndpoint()).port, + output: 'html', + logLevel: 'info', + }); + + console.log('Lighthouse scores:', results.lhr.categories.performance.score * 100); + + // ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ + const loadTestResults = await autocannon({ + url: 'http://localhost:3000', + connections: 10, + duration: 10, + }); + + console.log('Load test results:', { + 'Avg Latency': loadTestResults.latency.average, + 'Req/Sec': loadTestResults.requests.average, + }); + + await browser.close(); +} + +runPerformanceTests().catch(console.error); diff --git a/services/ahhachul.com/src/App.tsx b/services/ahhachul.com/src/App.tsx index ba7b9f4a1..95a91b312 100644 --- a/services/ahhachul.com/src/App.tsx +++ b/services/ahhachul.com/src/App.tsx @@ -1,8 +1,9 @@ -import { Global, ThemeProvider } from '@emotion/react'; +import React from 'react'; -import { QueryClientProvider, AuthProvider, NativeBridge } from '@/contexts'; import { StackFlow } from '@/stackflow'; -import { globalStyles, theme } from '@/styles'; + +import { UiComponent } from './components'; +import { useInitialLoader } from './hooks/domain/home/useInitialLoader'; if (import.meta.env.MODE === 'mock') { const { worker } = await import('@/mocks'); @@ -10,17 +11,13 @@ if (import.meta.env.MODE === 'mock') { } function App() { + useInitialLoader(); + return ( - - - - - - - - - - + + + + ); } diff --git a/services/ahhachul.com/src/apis/request/comment.ts b/services/ahhachul.com/src/apis/request/comment.ts new file mode 100644 index 000000000..32b1415a5 --- /dev/null +++ b/services/ahhachul.com/src/apis/request/comment.ts @@ -0,0 +1,53 @@ +import { sleep } from '@ahhachul/utils'; + +import axiosInstance from '@/apis/fetcher'; +import type { ApiResponse, Comment } from '@/types'; + +export const postComment = async (data: { + servicePath: string; + postId: number; + content: string; + upperCommentId: number | null; + isPrivate?: boolean; +}) => { + const { servicePath, postId, content, upperCommentId, isPrivate } = data; + + const response = await axiosInstance.post< + ApiResponse> + >(`/${servicePath}/${postId}/comments`, { + content, + upperCommentId, + isPrivate, + }); + + return response.data; +}; + +export const deleteComment = async (commentId: number) => { + const [response] = await Promise.allSettled([ + axiosInstance.delete>>(`/comments/${commentId}`), + sleep(750), + ]); + + if (response.status === 'rejected') { + throw response.reason; + } + + if (response.status === 'fulfilled') { + return response.value.data; + } + + // TODO: sentry์— ๋กœ๊ทธ ๋‚จ๊ธฐ๊ธฐ + throw new Error('Unexpected state in Promise.allSettled'); +}; + +export const updateComment = async (data: { content: string; commentId: number }) => { + const { content, commentId } = data; + const response = await axiosInstance.patch>>( + `/comments/${commentId}`, + { + content, + }, + ); + return response.data; +}; diff --git a/services/ahhachul.com/src/apis/request/community.ts b/services/ahhachul.com/src/apis/request/community.ts index 819be77f5..9a1280e3d 100644 --- a/services/ahhachul.com/src/apis/request/community.ts +++ b/services/ahhachul.com/src/apis/request/community.ts @@ -1,3 +1,5 @@ +import { appendFilesToFormData, createJsonBlob, extractFormData, sleep } from '@ahhachul/utils'; + import axiosInstance from '@/apis/fetcher'; import { type ApiResponse, @@ -11,7 +13,6 @@ import { WithPostId, CommunityEditForm, } from '@/types'; -import { appendFilesToFormData, createJsonBlob, extractFormData } from '@/utils'; export const fetchCommunityList = async (req: CommunityListParams) => { const endpoint = @@ -36,7 +37,7 @@ export const createCommunity = async (req: CommunityForm) => { formData.append('content', jsonBlob); if (req.images?.length) { - appendFilesToFormData(formData, req.images, 'imageFiles'); + appendFilesToFormData(formData, req.images); } const { data } = await axiosInstance.post>('/community-posts', formData, { @@ -52,7 +53,11 @@ export const fetchCommunityDetail = (id: number) => axiosInstance.get>(`/community-posts/${id}`); export const fetchCommunityCommentList = (id: number) => - axiosInstance.get>(`/community-posts/${id}/comments`); + axiosInstance.get>(`/community-posts/${id}/comments`, { + params: { + sort: 'createdAt,asc', + }, + }); export const editCommunity = async (id: number, req: CommunityEditForm) => { const formData = new FormData(); @@ -65,7 +70,6 @@ export const editCommunity = async (id: number, req: CommunityEditForm) => { appendFilesToFormData( formData, req.images.flatMap(image => (image.data !== null ? [image.data] : [])), - 'imageFiles', ); } @@ -81,3 +85,21 @@ export const editCommunity = async (id: number, req: CommunityEditForm) => { return data; }; + +export const deleteCommunity = async (articleId: number) => { + const [response] = await Promise.allSettled([ + axiosInstance.delete>(`/community-posts/${articleId}`), + sleep(750), + ]); + + if (response.status === 'rejected') { + throw response.reason; + } + + if (response.status === 'fulfilled') { + return response.value.data; + } + + // TODO: sentry์— ๋กœ๊ทธ ๋‚จ๊น€ + throw new Error('Unexpected state in Promise.allSettled'); +}; diff --git a/services/ahhachul.com/src/apis/request/complaint.ts b/services/ahhachul.com/src/apis/request/complaint.ts index cb0ff5c3b..b5e47f9de 100644 --- a/services/ahhachul.com/src/apis/request/complaint.ts +++ b/services/ahhachul.com/src/apis/request/complaint.ts @@ -1 +1,71 @@ -export {}; +import { appendFilesToFormData, createJsonBlob, extractFormData, sleep } from '@ahhachul/utils'; + +import axiosInstance from '@/apis/fetcher'; +import type { ApiResponse, CommentList, PaginatedList, WithPostId } from '@/types'; +import type { + ComplaintForm, + ComplaintListParams, + ComplaintPost, + ComplaintPostDetail, +} from '@/types/complaint'; + +export const fetchComplaintList = async (req: ComplaintListParams) => { + const { data } = await axiosInstance.get>>( + '/complaint-posts', + { + params: { + ...req, + pageSize: 10, + }, + }, + ); + return data; +}; + +export const createComplaint = async (req: ComplaintForm) => { + const formData = new FormData(); + const formDataWithoutImages = extractFormData(req, 'images'); + const jsonBlob = createJsonBlob(formDataWithoutImages); + + formData.append('content', jsonBlob); + + if (req.images?.length) { + appendFilesToFormData(formData, req.images); + } + + const { data } = await axiosInstance.post>('/complaint-posts', formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); + + return data; +}; + +export const fetchComplaintDetail = (id: number) => + axiosInstance.get>(`/complaint-posts/${id}`); + +export const fetchComplaintCommentList = (id: number) => + axiosInstance.get>(`/complaint-posts/${id}/comments`, { + params: { + sort: 'createdAt,asc', + }, + }); + +export const deleteComplaint = async (articleId: number) => { + const [response] = await Promise.allSettled([ + axiosInstance.delete>(`/complaint-posts/${articleId}`), + sleep(750), + ]); + + if (response.status === 'rejected') { + throw response.reason; + } + + if (response.status === 'fulfilled') { + return response.value.data; + } + + // TODO: sentry์— ๋กœ๊ทธ ๋‚จ๊น€ + throw new Error('Unexpected state in Promise.allSettled'); +}; diff --git a/services/ahhachul.com/src/apis/request/index.ts b/services/ahhachul.com/src/apis/request/index.ts index 263750132..81c31adec 100644 --- a/services/ahhachul.com/src/apis/request/index.ts +++ b/services/ahhachul.com/src/apis/request/index.ts @@ -1,5 +1,7 @@ export * from './auth'; export * from './user'; export * from './token'; +export * from './comment'; export * from './lostFound'; export * from './community'; +export * from './complaint'; diff --git a/services/ahhachul.com/src/apis/request/lostFound.ts b/services/ahhachul.com/src/apis/request/lostFound.ts index d18853f2b..2e28f3247 100644 --- a/services/ahhachul.com/src/apis/request/lostFound.ts +++ b/services/ahhachul.com/src/apis/request/lostFound.ts @@ -1,3 +1,5 @@ +import { appendFilesToFormData, createJsonBlob, extractFormData, sleep } from '@ahhachul/utils'; + import axiosInstance from '@/apis/fetcher'; import type { ApiResponse, @@ -9,8 +11,8 @@ import type { CommentList, WithPostId, LostFoundEditForm, + LostStatus, } from '@/types'; -import { appendFilesToFormData, createJsonBlob, extractFormData } from '@/utils'; export const fetchLostFoundList = async (req: LostFoundListParams) => { const { data } = await axiosInstance.get>>( @@ -49,7 +51,11 @@ export const fetchLostFoundDetail = (id: number) => axiosInstance.get>(`/lost-posts/${id}`); export const fetchLostFoundCommentList = (id: number) => - axiosInstance.get>(`/lost-posts/${id}/comments`); + axiosInstance.get>(`/lost-posts/${id}/comments`, { + params: { + sort: 'createdAt,asc', + }, + }); export const editLostFound = async (id: number, req: LostFoundEditForm) => { const formData = new FormData(); @@ -77,3 +83,39 @@ export const editLostFound = async (id: number, req: LostFoundEditForm) => { return data; }; + +export const deleteLostFound = async (articleId: number) => { + const [response] = await Promise.allSettled([ + axiosInstance.delete>(`/lost-posts/${articleId}`), + sleep(750), + ]); + + if (response.status === 'rejected') { + throw response.reason; + } + + if (response.status === 'fulfilled') { + return response.value.data; + } + + // TODO: sentry์— ๋กœ๊ทธ ๋‚จ๊น€ + throw new Error('Unexpected state in Promise.allSettled'); +}; + +export const updateLostFoundStatus = async (articleId: number, status: LostStatus) => { + const [response] = await Promise.allSettled([ + axiosInstance.patch>(`/lost-posts/${articleId}/status`, { status }), + sleep(750), + ]); + + if (response.status === 'rejected') { + throw response.reason; + } + + if (response.status === 'fulfilled') { + return response.value.data; + } + + // TODO: sentry์— ๋กœ๊ทธ ๋‚จ๊น€ + throw new Error('Unexpected state in Promise.allSettled'); +}; diff --git a/services/ahhachul.com/src/apis/request/subway.ts b/services/ahhachul.com/src/apis/request/subway.ts new file mode 100644 index 000000000..f6c49b243 --- /dev/null +++ b/services/ahhachul.com/src/apis/request/subway.ts @@ -0,0 +1,33 @@ +import axios from 'axios'; + +import { sleep } from '@ahhachul/utils'; + +import axiosInstance from '@/apis/fetcher'; +import { + ITrain, + SubwayLineServerModel, + WithSubwayLineId, + WithSubwayStationId, + type ApiResponse, +} from '@/types'; + +import { BASE_URL } from '../baseUrl'; +import { API_PREFIX } from '../endpointPrefix'; + +interface APITrainInfoParams extends WithSubwayLineId, WithSubwayStationId {} +interface APITrainInfoResponse { + trainRealTimes: ITrain[]; +} + +export const fetchSubwayLines = async () => + await axiosInstance.get>('/subway-lines'); + +export const prefetchSubwayLines = async () => + await axios.get>( + `${BASE_URL.SERVER}${API_PREFIX}/subway-lines`, + ); + +export const fetchTrainInfo = async (params: APITrainInfoParams) => { + await sleep(400); + return axiosInstance.get>('/trains/real-times', { params }); +}; diff --git a/services/ahhachul.com/src/apis/request/user.ts b/services/ahhachul.com/src/apis/request/user.ts index 7f0d518ae..c30b8015c 100644 --- a/services/ahhachul.com/src/apis/request/user.ts +++ b/services/ahhachul.com/src/apis/request/user.ts @@ -1,5 +1,17 @@ +import axios from 'axios'; + import axiosInstance from '@/apis/fetcher'; -import type { ApiResponse, UserProfileResponseDto } from '@/types'; +import type { + ApiResponse, + APIUpdateUserResponse, + AuthTokens, + UserFavoriteStations, + UserProfileResponseDto, +} from '@/types'; +import { getAccessTokenInLocalStorage } from '@/utils/localStorage'; + +import { BASE_URL } from '../baseUrl'; +import { API_PREFIX } from '../endpointPrefix'; export const fetchUserProfile = async () => { const { data } = await axiosInstance.get>('/members'); @@ -7,10 +19,74 @@ export const fetchUserProfile = async () => { return data; }; +export const prefetchUserProfile = async () => { + const accessToken = getAccessTokenInLocalStorage(); + + const { data } = await axios.get>( + `${BASE_URL.SERVER}${API_PREFIX}/members`, + { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }, + ); + + return data; +}; + export const fetchUserFavoriteStations = async () => { - const { data } = await axiosInstance.get>( + const { data } = await axiosInstance.get>( '/members/bookmarks/stations', ); return data; }; + +export const prefetchUserFavoriteStations = async () => { + const accessToken = getAccessTokenInLocalStorage(); + + const { data } = await axios.get>( + `${BASE_URL.SERVER}${API_PREFIX}/members/bookmarks/stations`, + { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }, + ); + + return data; +}; + +export const createUserFavoriteStations = async (stations: any) => { + const response = await axiosInstance.post>( + '/members/bookmarks/stations', + { stations }, + // { stations: stations.map((item: any) => ({ ...item, stationName: item.stationName + '์—ญ' })) }, + ); + + return response.data; +}; + +export const updateUser = async (data: { nickname: string; auth: AuthTokens }) => { + try { + const accessToken = data.auth.accessToken; + const res = await axios.patch( + `${import.meta.env.VITE_BASE_URL}/v1/members`, + { nickname: data.nickname }, + { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }, + ); + + return res.data; + } catch (error) { + if (axios.isAxiosError(error)) { + throw new Error(`Update user failed: ${error.response?.data?.message || error.message}`); + } else { + console.error('Unexpected error during user update:', error); + throw new Error('An unexpected error occurred during user update.'); + } + } +}; diff --git a/services/ahhachul.com/src/assets/graphics/character/index.ts b/services/ahhachul.com/src/assets/graphics/character/index.ts index 1b21f38b9..cce690e27 100644 --- a/services/ahhachul.com/src/assets/graphics/character/index.ts +++ b/services/ahhachul.com/src/assets/graphics/character/index.ts @@ -1,2 +1,3 @@ export { default as ErrorGraphic } from './error.svg?react'; +export { default as NoticeGraphic } from './notice.svg?react'; export { default as EmptyGraphic } from './no-results.svg?react'; diff --git a/services/ahhachul.com/src/assets/graphics/character/notice.svg b/services/ahhachul.com/src/assets/graphics/character/notice.svg new file mode 100644 index 000000000..6d616d720 --- /dev/null +++ b/services/ahhachul.com/src/assets/graphics/character/notice.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/services/ahhachul.com/src/assets/icons/complaint/ic_arrow.svg b/services/ahhachul.com/src/assets/icons/complaint/ic_arrow.svg new file mode 100644 index 000000000..4b36c9a1a --- /dev/null +++ b/services/ahhachul.com/src/assets/icons/complaint/ic_arrow.svg @@ -0,0 +1,4 @@ + + + + diff --git a/services/ahhachul.com/src/assets/icons/complaint/ic_emergency.svg b/services/ahhachul.com/src/assets/icons/complaint/ic_emergency.svg new file mode 100644 index 000000000..1361c1818 --- /dev/null +++ b/services/ahhachul.com/src/assets/icons/complaint/ic_emergency.svg @@ -0,0 +1,4 @@ + + + + diff --git a/services/ahhachul.com/src/assets/icons/complaint/ic_hit.svg b/services/ahhachul.com/src/assets/icons/complaint/ic_hit.svg new file mode 100644 index 000000000..ba7ff16ce --- /dev/null +++ b/services/ahhachul.com/src/assets/icons/complaint/ic_hit.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/services/ahhachul.com/src/assets/icons/complaint/ic_metro.svg b/services/ahhachul.com/src/assets/icons/complaint/ic_metro.svg new file mode 100644 index 000000000..16073904f --- /dev/null +++ b/services/ahhachul.com/src/assets/icons/complaint/ic_metro.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/services/ahhachul.com/src/assets/icons/complaint/ic_tree.svg b/services/ahhachul.com/src/assets/icons/complaint/ic_tree.svg new file mode 100644 index 000000000..d70bafcd7 --- /dev/null +++ b/services/ahhachul.com/src/assets/icons/complaint/ic_tree.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/services/ahhachul.com/src/assets/icons/complaint/index.ts b/services/ahhachul.com/src/assets/icons/complaint/index.ts new file mode 100644 index 000000000..fbc5befbf --- /dev/null +++ b/services/ahhachul.com/src/assets/icons/complaint/index.ts @@ -0,0 +1,5 @@ +export { default as ArrowMiniIcon } from './ic_arrow.svg?react'; +export { default as EmergencyIcon } from './ic_emergency.svg?react'; +export { default as HitIcon } from './ic_hit.svg?react'; +export { default as MetroIcon } from './ic_metro.svg?react'; +export { default as TreeIcon } from './ic_tree.svg?react'; diff --git a/services/ahhachul.com/src/assets/icons/jsx/icons.tsx b/services/ahhachul.com/src/assets/icons/jsx/icons.tsx new file mode 100644 index 000000000..25a656565 --- /dev/null +++ b/services/ahhachul.com/src/assets/icons/jsx/icons.tsx @@ -0,0 +1,250 @@ +function RecoveryPhraseIcon() { + return ( + + Recovery Phrase + + + + + + + ); +} + +function ShieldIcon() { + return ( + + Shield + + + + ); +} + +function PassIcon() { + return ( + + Pass + + + + + + + + + ); +} + +function BannedIcon() { + return ( + + Banned + + + + ); +} + +function FaceIDIcon() { + return ( + + Face ID + + + ); +} + +function DangerIcon() { + return ( + + Danger + + + + + ); +} + +function CloseIcon() { + return ( + + Close + + + + ); +} + +function LockIcon() { + return ( + + Lock + + + + ); +} + +function WarningIcon() { + return ( + + Warning + + + + + ); +} + +function PhraseIcon() { + return ( + + Phrase + + + + + + + + + ); +} + +function LostIcon() { + return ( + + + + + ); +} + +function CrossIcon() { + return ( + + Cross + + + + ); +} + +export { + RecoveryPhraseIcon, + ShieldIcon, + PassIcon, + BannedIcon, + FaceIDIcon, + DangerIcon, + CloseIcon, + LockIcon, + WarningIcon, + PhraseIcon, + CrossIcon, + LostIcon, +}; diff --git a/services/ahhachul.com/src/assets/icons/my/app-link.svg b/services/ahhachul.com/src/assets/icons/my/app-link.svg new file mode 100644 index 000000000..ef4b7e390 --- /dev/null +++ b/services/ahhachul.com/src/assets/icons/my/app-link.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/services/ahhachul.com/src/assets/icons/my/qna.svg b/services/ahhachul.com/src/assets/icons/my/qna.svg new file mode 100644 index 000000000..6a798c051 --- /dev/null +++ b/services/ahhachul.com/src/assets/icons/my/qna.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/services/ahhachul.com/src/assets/icons/setting/ic_home.svg b/services/ahhachul.com/src/assets/icons/setting/ic_home.svg new file mode 100644 index 000000000..2aa1fc54e --- /dev/null +++ b/services/ahhachul.com/src/assets/icons/setting/ic_home.svg @@ -0,0 +1,3 @@ + + + diff --git a/services/ahhachul.com/src/assets/icons/setting/ic_office.svg b/services/ahhachul.com/src/assets/icons/setting/ic_office.svg new file mode 100644 index 000000000..834a0bc35 --- /dev/null +++ b/services/ahhachul.com/src/assets/icons/setting/ic_office.svg @@ -0,0 +1,3 @@ + + + diff --git a/services/ahhachul.com/src/assets/icons/setting/ic_school.svg b/services/ahhachul.com/src/assets/icons/setting/ic_school.svg new file mode 100644 index 000000000..3616eac07 --- /dev/null +++ b/services/ahhachul.com/src/assets/icons/setting/ic_school.svg @@ -0,0 +1,3 @@ + + + diff --git a/services/ahhachul.com/src/assets/icons/setting/ic_star.svg b/services/ahhachul.com/src/assets/icons/setting/ic_star.svg new file mode 100644 index 000000000..d15c650e5 --- /dev/null +++ b/services/ahhachul.com/src/assets/icons/setting/ic_star.svg @@ -0,0 +1,3 @@ + + + diff --git a/services/ahhachul.com/src/assets/icons/setting/index.ts b/services/ahhachul.com/src/assets/icons/setting/index.ts new file mode 100644 index 000000000..b0a2bb6a7 --- /dev/null +++ b/services/ahhachul.com/src/assets/icons/setting/index.ts @@ -0,0 +1,4 @@ +export { default as HomeMiniIcon } from './ic_home.svg?react'; +export { default as StarMiniIcon } from './ic_star.svg?react'; +export { default as SchoolMiniIcon } from './ic_school.svg?react'; +export { default as OfficeMiniIcon } from './ic_office.svg?react'; diff --git a/services/ahhachul.com/src/assets/icons/system/ic_checkbox.svg b/services/ahhachul.com/src/assets/icons/system/ic_checkbox.svg new file mode 100644 index 000000000..bf696bdaf --- /dev/null +++ b/services/ahhachul.com/src/assets/icons/system/ic_checkbox.svg @@ -0,0 +1,15 @@ + + + \ No newline at end of file diff --git a/services/ahhachul.com/src/assets/icons/system/ic_chevron.svg b/services/ahhachul.com/src/assets/icons/system/ic_chevron.svg index 876525ab7..fca5cce7d 100644 --- a/services/ahhachul.com/src/assets/icons/system/ic_chevron.svg +++ b/services/ahhachul.com/src/assets/icons/system/ic_chevron.svg @@ -1,6 +1,6 @@ - + diff --git a/services/ahhachul.com/src/assets/icons/system/ic_list.svg b/services/ahhachul.com/src/assets/icons/system/ic_list.svg new file mode 100644 index 000000000..37d025c72 --- /dev/null +++ b/services/ahhachul.com/src/assets/icons/system/ic_list.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/services/ahhachul.com/src/assets/icons/system/ic_minus.svg b/services/ahhachul.com/src/assets/icons/system/ic_minus.svg new file mode 100644 index 000000000..02de7b6e2 --- /dev/null +++ b/services/ahhachul.com/src/assets/icons/system/ic_minus.svg @@ -0,0 +1,4 @@ + + + + diff --git a/services/ahhachul.com/src/assets/icons/system/ic_retry.svg b/services/ahhachul.com/src/assets/icons/system/ic_retry.svg new file mode 100644 index 000000000..6611ea27a --- /dev/null +++ b/services/ahhachul.com/src/assets/icons/system/ic_retry.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/services/ahhachul.com/src/assets/icons/system/ic_transfer.svg b/services/ahhachul.com/src/assets/icons/system/ic_transfer.svg new file mode 100644 index 000000000..1a1190ce5 --- /dev/null +++ b/services/ahhachul.com/src/assets/icons/system/ic_transfer.svg @@ -0,0 +1,3 @@ + + + diff --git a/services/ahhachul.com/src/assets/icons/system/index.ts b/services/ahhachul.com/src/assets/icons/system/index.ts index 60f5930c0..15a9f9b90 100644 --- a/services/ahhachul.com/src/assets/icons/system/index.ts +++ b/services/ahhachul.com/src/assets/icons/system/index.ts @@ -1,20 +1,27 @@ +export { default as LogoIcon } from './logo.svg?react'; + export { default as DotIcon } from './ic_dot.svg?react'; export { default as MicIcon } from './ic_mic.svg?react'; export { default as BellIcon } from './ic_bell.svg?react'; +export { default as ListIcon } from './ic_list.svg?react'; export { default as TalkIcon } from './ic_talk.svg?react'; export { default as InfoIcon } from './ic_info.svg?react'; -export { default as LogoIcon } from './ic_logo.svg?react'; export { default as HomeIcon } from './ic_home.svg?react'; export { default as PlusIcon } from './ic_plus.svg?react'; +export { default as MinusIcon } from './ic_minus.svg?react'; +export { default as RetryIcon } from './ic_retry.svg?react'; export { default as ShareIcon } from './ic_share.svg?react'; export { default as CloseIcon } from './ic_close.svg?react'; export { default as CheckIcon } from './ic_check.svg?react'; +export { default as LogoTextIcon } from './ic_logo.svg?react'; export { default as SearchIcon } from './ic_search.svg?react'; export { default as PictureIcon } from './ic_picture.svg?react'; export { default as WarningIcon } from './ic_warning.svg?react'; export { default as ProfileIcon } from './ic_profile.svg?react'; export { default as CommentIcon } from './ic_comment.svg?react'; export { default as ChevronIcon } from './ic_chevron.svg?react'; +export { default as TransferIcon } from './ic_transfer.svg?react'; +export { default as CheckboxIcon } from './ic_checkbox.svg?react'; export { default as BookmarkIcon } from './ic_bookmark.svg?react'; export { default as SmallLogeIcon } from './ic_logo_sm.svg?react'; export { default as EllipsisIcon } from './ic_ellipsis.svg?react'; diff --git a/services/ahhachul.com/src/assets/icons/system/logo.svg b/services/ahhachul.com/src/assets/icons/system/logo.svg new file mode 100644 index 000000000..68cadeca2 --- /dev/null +++ b/services/ahhachul.com/src/assets/icons/system/logo.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/services/ahhachul.com/src/assets/images/default_thumbnail.svg b/services/ahhachul.com/src/assets/images/default_thumbnail.svg new file mode 100644 index 000000000..e69238e99 --- /dev/null +++ b/services/ahhachul.com/src/assets/images/default_thumbnail.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/services/ahhachul.com/src/assets/images/icon_camera.png b/services/ahhachul.com/src/assets/images/icon_camera.png new file mode 100644 index 000000000..8fbc8a33a Binary files /dev/null and b/services/ahhachul.com/src/assets/images/icon_camera.png differ diff --git a/services/ahhachul.com/src/components/common/actionSheet/ActionSheet.component.tsx b/services/ahhachul.com/src/components/common/actionSheet/ActionSheet.component.tsx deleted file mode 100644 index 2725dd4e2..000000000 --- a/services/ahhachul.com/src/components/common/actionSheet/ActionSheet.component.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { useDisableScroll } from '@/hooks'; - -import * as S from './ActionSheet.styled'; - -interface ActionSheetProps { - isOpen: boolean; - onClose: () => void; - actions: { - label: string; - onClick: () => void; - }[]; -} - -const ActionSheet = ({ isOpen, onClose, actions }: ActionSheetProps) => { - useDisableScroll(); - - if (!isOpen) return null; - - return ( - <> - - - - {actions.map(action => ( - { - action.onClick(); - onClose(); - }} - > - {action.label} - - ))} - - ์ทจ์†Œ - - - ); -}; - -export default ActionSheet; diff --git a/services/ahhachul.com/src/components/common/actionSheet/ActionSheet.styled.tsx b/services/ahhachul.com/src/components/common/actionSheet/ActionSheet.styled.tsx deleted file mode 100644 index 21b0fc9b7..000000000 --- a/services/ahhachul.com/src/components/common/actionSheet/ActionSheet.styled.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { css } from '@emotion/react'; -import styled from '@emotion/styled'; - -export const Dim = styled.div` - ${({ theme }) => css` - position: fixed; - inset: 0; - background-color: ${theme.colors.dim}; - z-index: ${theme.zIndex.dim}; - transition: opacity 0.3s ease; - `} -`; - -export const Sheet = styled.div` - ${({ theme }) => css` - position: fixed; - bottom: 0; - left: 0; - right: 0; - z-index: ${theme.zIndex.drawer}; - display: flex; - flex-direction: column; - padding: 16px; - padding-bottom: 40px; - animation: slideIn 0.3s ease-out; - - @keyframes slideIn { - from { - transform: translateY(100%); - } - to { - transform: translateY(0); - } - } - `} -`; - -export const Container = styled.div` - ${({ theme }) => css` - border-radius: 16px; - display: flex; - flex-direction: column; - overflow: hidden; - - & > button:not(:last-of-type) { - border-bottom: 1px solid ${theme.colors.gray[20]}; - } - `} -`; - -export const ActionButton = styled.button` - ${({ theme }) => css` - ${theme.fonts.bodyLargeSemi}; - - width: 100%; - height: 52px; - background-color: ${theme.colors.white}; - color: ${theme.colors.black}; - - cursor: pointer; - `} -`; - -export const CancelButton = styled(ActionButton)` - margin-top: 10px; - border-radius: 16px; -`; diff --git a/services/ahhachul.com/src/components/common/actionSheet/index.ts b/services/ahhachul.com/src/components/common/actionSheet/index.ts deleted file mode 100644 index e50c5a9b0..000000000 --- a/services/ahhachul.com/src/components/common/actionSheet/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as ActionSheet } from './ActionSheet.component'; diff --git a/services/ahhachul.com/src/components/common/appErrorBoundary/ErrorBoundary.component.tsx b/services/ahhachul.com/src/components/common/appErrorBoundary/ErrorBoundary.component.tsx index 1cba636ba..8753342c2 100644 --- a/services/ahhachul.com/src/components/common/appErrorBoundary/ErrorBoundary.component.tsx +++ b/services/ahhachul.com/src/components/common/appErrorBoundary/ErrorBoundary.component.tsx @@ -2,7 +2,7 @@ import React from 'react'; import type { AxiosError } from 'axios'; -import { isChangedArray } from '@/utils'; +import { isChangedArray } from '@ahhachul/utils'; type ErrorFallbackProps = { error: AxiosError; diff --git a/services/ahhachul.com/src/components/common/avatar/Avatar.component.tsx b/services/ahhachul.com/src/components/common/avatar/Avatar.component.tsx new file mode 100644 index 000000000..62c266810 --- /dev/null +++ b/services/ahhachul.com/src/components/common/avatar/Avatar.component.tsx @@ -0,0 +1,29 @@ +import styled from '@emotion/styled'; + +import DefaultThumbnail from '@/assets/images/default_thumbnail.svg'; + +type Props = { + src?: string; + size?: number; + minSize?: boolean; +}; + +export const Avatar = ({ src, size, minSize = true }: Props) => { + return ( + + ); +}; + +const StyledAvatar = styled.img` + width: ${props => (props.size ? props.size + 'px' : '60px')}; + height: ${props => (props.size ? props.size + 'px' : '60px')}; + border-radius: 50%; + min-width: ${props => (props.minSize ? '48px' : 'unset')}; + min-height: ${props => (props.minSize ? '48px' : 'unset')}; + object-fit: cover; +`; diff --git a/services/ahhachul.com/src/components/common/button/SmoothButton.component.tsx b/services/ahhachul.com/src/components/common/button/SmoothButton.component.tsx new file mode 100644 index 000000000..d3c6c6300 --- /dev/null +++ b/services/ahhachul.com/src/components/common/button/SmoothButton.component.tsx @@ -0,0 +1,118 @@ +import React from 'react'; + +import { keyframes } from '@emotion/react'; +import styled from '@emotion/styled'; +import { SymbolIcon } from '@radix-ui/react-icons'; +import { AnimatePresence, motion } from 'motion/react'; + +function AnimatedState({ + children, + className, + state, +}: { + children: React.ReactNode; + className?: string; + state: string; +}) { + const variants = { + initial: { opacity: 0, y: -25 }, + visible: { opacity: 1, y: 0 }, + exit: { opacity: 0, y: 25 }, + }; + + return ( + + + {children} + + + ); +} + +interface Props { + status: 'error' | 'idle' | 'pending' | 'success'; + idleText?: string | React.ReactNode; + successText?: string; + errorText?: string; + className?: string; + handleClick?: () => void; +} + +export const SmoothButton = ({ + status, + idleText = 'ํ™•์ธ', + successText = '์‚ญ์ œ ์™„๋ฃŒ', + errorText = '์˜ค๋ฅ˜ ๋ฐœ์ƒ', + className, + handleClick, +}: Props) => { + const buttonContent = { + idle: idleText, + pending: , + success: successText, + error: errorText, + }; + + return ( + { + if (status === 'success' || status === 'error') return; + handleClick?.(); + }} + > + {buttonContent[status]} + + ); +}; + +const spin = keyframes` + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +`; + +const StyledButton = styled.button` + position: relative; + height: 32px; + width: 144px; + overflow: hidden; + border-radius: 8px; + font-size: 14px; + font-weight: 500; + box-shadow: + 0 1px 3px 0 rgba(0, 0, 0, 0.1), + 0 1px 2px 0 rgba(0, 0, 0, 0.06); + + &:disabled { + opacity: 0.9; + cursor: not-allowed; + } +`; + +const AnimatedStateWrapper = styled(AnimatedState)` + display: flex; + width: 100%; + align-items: center; + justify-content: center; + color: white; +`; + +const SpinningIcon = styled(SymbolIcon)` + width: 16px; + height: 16px; + animation: ${spin} 1s linear infinite; +`; diff --git a/services/ahhachul.com/src/components/common/checkbox/Checkbox.component.tsx b/services/ahhachul.com/src/components/common/checkbox/Checkbox.component.tsx new file mode 100644 index 000000000..afca1b832 --- /dev/null +++ b/services/ahhachul.com/src/components/common/checkbox/Checkbox.component.tsx @@ -0,0 +1,108 @@ +import { ChangeEvent } from 'react'; + +import { css } from '@emotion/react'; +import styled from '@emotion/styled'; + +import { CheckboxIcon } from '@/assets/icons/system'; + +interface CheckboxProps { + checked?: boolean; + disabled?: boolean; + onChange?: (e: ChangeEvent) => void; + label?: string; + name?: string; + value?: string; + className?: string; +} + +const CheckboxContainer = styled.label<{ disabled?: boolean }>` + display: inline-flex; + align-items: center; + gap: 0.5rem; + cursor: ${props => (props.disabled ? 'not-allowed' : 'pointer')}; + user-select: none; +`; + +const CheckboxInput = styled.input` + position: absolute; + opacity: 0; + width: 0; + height: 0; + margin: 0; +`; + +const CheckboxControl = styled.div<{ checked?: boolean; disabled?: boolean }>` + width: 20px; + height: 20px; + border-radius: 4px; + border: 2px solid #eaeaec; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s ease; + background-color: #ffffff; + cursor: pointer; + + ${({ checked, disabled }) => { + if (checked && !disabled) { + return css` + background-color: #2acf6c; + border-color: #2acf6c; + `; + } + if (checked && disabled) { + return css` + background-color: #b6efdb; + border-color: #b6efdb; + `; + } + if (!checked && disabled) { + return css` + background-color: #f7fdfb; + border-color: #b6efdb; + `; + } + return ''; + }} + + svg { + color: ${props => (props.disabled ? '#84E4C2' : '#ffffff')}; + opacity: ${props => (props.checked ? 1 : 0)}; + transition: opacity 0.2s ease; + } +`; + +const CheckboxLabel = styled.span<{ disabled?: boolean }>` + color: ${props => (props.disabled ? '#95979F' : '#33333E')}; + font-size: 13px; + cursor: pointer; +`; + +export default function Checkbox({ + checked = false, + disabled = false, + onChange, + label, + name, + value, + className, + ...props +}: CheckboxProps) { + return ( + + + + + + {label && {label}} + + ); +} diff --git a/services/ahhachul.com/src/components/common/checkbox/index.ts b/services/ahhachul.com/src/components/common/checkbox/index.ts new file mode 100644 index 000000000..63ae8aa5b --- /dev/null +++ b/services/ahhachul.com/src/components/common/checkbox/index.ts @@ -0,0 +1 @@ +export { default as Checkbox } from './Checkbox.component'; diff --git a/services/ahhachul.com/src/components/common/comment/CommentList.component.tsx b/services/ahhachul.com/src/components/common/comment/CommentList.component.tsx index 3552b1597..16ba9ebdd 100644 --- a/services/ahhachul.com/src/components/common/comment/CommentList.component.tsx +++ b/services/ahhachul.com/src/components/common/comment/CommentList.component.tsx @@ -1,5 +1,6 @@ -import React from 'react'; +import React, { useEffect } from 'react'; +import { useTempComment } from '@/stores/comment'; import type { CommentList } from '@/types'; import Comment from './commentListItem/CommentListItem.component'; @@ -7,24 +8,45 @@ import EmptyCommentList from './emptyCommentList/EmptyCommentList.component'; interface BaseCommentListProps { commentsMap: CommentList['comments']; + servicePath: string; + queryKey: readonly unknown[]; + isArticleAuthor: boolean; } -const BaseCommentList = React.memo(({ commentsMap }: BaseCommentListProps) => { - if (commentsMap.length === 0) return ; - - return ( - <> - {commentsMap.map(({ parentComment, childComments }) => ( - - - {childComments.map(childComment => ( - - ))} - - ))} - - ); -}); +const BaseCommentList = React.memo( + ({ queryKey, servicePath, commentsMap, isArticleAuthor }: BaseCommentListProps) => { + const { setTempComment } = useTempComment(); + + useEffect(() => setTempComment(commentsMap), [commentsMap]); + + if (commentsMap.length === 0) return ; + + return ( + <> + {commentsMap.map(({ parentComment, childComments }) => ( + + + {childComments.map(childComment => ( + + ))} + + ))} + + ); + }, +); BaseCommentList.displayName = 'BaseCommentList'; diff --git a/services/ahhachul.com/src/components/common/comment/commentActions/CommentActions.component.tsx b/services/ahhachul.com/src/components/common/comment/commentActions/CommentActions.component.tsx new file mode 100644 index 000000000..c1c397019 --- /dev/null +++ b/services/ahhachul.com/src/components/common/comment/commentActions/CommentActions.component.tsx @@ -0,0 +1,317 @@ +import React, { useCallback, useMemo, useRef, useState } from 'react'; +import useMeasure from 'react-use-measure'; + +import styled from '@emotion/styled'; +import { useQueryClient } from '@tanstack/react-query'; +import { AnimatePresence, motion } from 'motion/react'; +import { Drawer } from 'vaul'; + +import { CloseIcon, DangerIcon, PhraseIcon, WarningIcon } from '@/assets/icons/jsx/icons'; +import { EllipsisIcon } from '@/assets/icons/system'; +import { useDeleteComment } from '@/services/comment'; +import { useFlow } from '@/stackflow'; + +import * as S from './CommentActions.styled'; + +export interface CommentDropEllipsisProps { + articleId: string; + commentId: number; + isAuthor: boolean; + queryKey: readonly unknown[]; +} + +export const CommentDropEllipsis = ({ + articleId, + commentId, + isAuthor, + queryKey, +}: CommentDropEllipsisProps): React.ReactElement => { + const [isOpen, setIsOpen] = useState(false); + const [view, setView] = useState('default'); + const [elementRef, bounds] = useMeasure(); + const previousHeightRef = useRef(0); + + const handleOpen = () => { + setView('default'); + setTimeout(() => { + setIsOpen(true); + }, 100); + }; + const handleClose = () => setIsOpen(false); + + const { push } = useFlow(); + const handleEdit = () => { + handleClose(); + setTimeout(() => { + push('EditCommentPage', { + commentId, + id: +articleId, + queryKey, + }); + }, 500); + }; + + const content = useMemo(() => { + switch (view) { + case 'default': + return isAuthor ? ( + + ) : ( + + ); + case 'remove': + return ( + + ); + } + }, [view, queryKey]); + + const opacityDuration = useMemo(() => { + const MIN_DURATION = 0.15; + const MAX_DURATION = 0.27; + + if (!previousHeightRef.current) { + previousHeightRef.current = bounds.height; + return MIN_DURATION; + } + + const heightDifference = Math.abs(bounds.height - previousHeightRef.current); + previousHeightRef.current = bounds.height; + + const duration = Math.min(Math.max(heightDifference / 500, MIN_DURATION), MAX_DURATION); + + return duration; + }, [bounds.height]); + + return ( +
+ + + + + + + + + + + + + + + + + {content} + + + + + + + +
+ ); +}; + +function Header({ + icon, + title, + description, +}: { + icon: React.ReactNode; + title: string; + description: string; +}) { + return ( + + {icon} + {title} + {description} + + ); +} + +function DefaultView({ + setView, + handleEdit, +}: { + setView: (view: string) => void; + handleEdit: () => void; +}) { + return ( + <> + + ์„ค์ • + + + + + ์ˆ˜์ •ํ•˜๊ธฐ + + setView('remove')}> + + ์‚ญ์ œํ•˜๊ธฐ + + + + ); +} + +function ReportView({ handleClose }: { handleClose: () => void }) { + return ( + <> + + ์„ค์ • + + + + + ์‹ ๊ณ ํ•˜๊ธฐ + + + + ); +} + +function RemoveComment({ + articleId, + commentId, + queryKey, + setView, + handleClose, +}: { + articleId: string; + commentId: number; + queryKey: readonly unknown[]; + setView: (view: string) => void; + handleClose: () => void; +}) { + const queryClient = useQueryClient(); + const { mutate: deleteComment, status, isPending } = useDeleteComment(+articleId); + + const handleDeleteComment = useCallback( + () => + deleteComment(commentId, { + onSuccess: () => { + setTimeout(() => { + handleClose(); + }, 350); + setTimeout(() => { + queryClient.invalidateQueries({ + queryKey, + }); + }, 550); + }, + }), + [articleId, commentId], + ); + + return ( +
+
+
} + title="๋Œ“๊ธ€์„ ์‚ญ์ œํ•˜์‹œ๊ฒ ์–ด์š”?" + description="์‚ญ์ œํ•˜์‹œ๋ฉด ๋ณต๊ตฌํ•  ์ˆ˜ ์—†์–ด์š”. ํ•ด๋‹น ๋Œ“๊ธ€์„ ์‚ญ์ œํ• ๊นŒ์š”?" + /> + + setView('default')} + > + ์ทจ์†Œ + + + +
+
+ ); +} + +const DrawerButton = styled.button` + height: 24px; + font-weight: 500; + color: black; + transition: background-color 0.2s; + display: flex; + align-items: center; + width: 100%; + justify-content: flex-end; + &:focus-visible { + box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.5); + } +`; + +const DrawerOverlay = styled(Drawer.Overlay)` + position: fixed; + inset: 0; + z-index: 10000; + background-color: rgba(0, 0, 0, 0.3); + transition: opacity 0.2s cubic-bezier(0.165, 0.84, 0.44, 1); +`; + +const DrawerContentWrapper = styled(motion.div)` + position: fixed; + left: 16px; + right: 16px; + bottom: 16px; + z-index: 10010; + max-width: 360px; + margin-left: auto; + margin-right: auto; + overflow: hidden; + border-radius: 36px; + background-color: #ffffff; + outline: none; + transition: transform 0.2s cubic-bezier(0.165, 0.84, 0.44, 1); +`; + +const CloseButton = styled.button` + position: absolute; + top: 28px; + right: 32px; + z-index: 10; + display: flex; + height: 32px; + width: 32px; + align-items: center; + justify-content: center; + border-radius: 9999px; + background-color: #f7f8f9; + color: #949595; + transition: transform 0.2s; + &:focus { + transform: scale(0.95); + } + &:active { + transform: scale(0.75); + } +`; + +const ContentWrapper = styled.div` + padding: 10px 24px 24px; +`; diff --git a/services/ahhachul.com/src/components/common/comment/commentActions/CommentActions.styled.ts b/services/ahhachul.com/src/components/common/comment/commentActions/CommentActions.styled.ts new file mode 100644 index 000000000..aa5fa3280 --- /dev/null +++ b/services/ahhachul.com/src/components/common/comment/commentActions/CommentActions.styled.ts @@ -0,0 +1,130 @@ +import { type Interpolation, type Theme, css } from '@emotion/react'; +import styled from '@emotion/styled'; + +import { SmoothButton } from '../../button/SmoothButton.component'; + +export const buttonFilter = () => + ({ + flexShrink: 0, + height: '30px', + display: 'flex', + alignItems: 'center', + justifyContent: 'flex-end', + margin: 0, + }) as Interpolation; + +export const buttonBase = css` + display: flex; + height: 48px; + width: 100%; + align-items: center; + gap: 15px; + border-radius: 16px; + padding: 0 16px; + font-weight: 600; + font-size: 17px; + transition: transform 0.2s; + + &:focus { + transform: scale(0.95); + } + &:active { + transform: scale(0.95); + } +`; + +export const Button = styled.button` + ${buttonBase} + background-color: #F7F8F9; + color: #222222; +`; + +export const SecondaryButton = styled.button<{ + variant?: 'default' | 'primary' | 'danger'; +}>` + ${buttonBase} + justify-content: center; + border-radius: 9999px; + font-size: 19px; + background-color: ${props => + props.variant === 'primary' ? '#4DAFFF' : props.variant === 'danger' ? '#FF3F40' : '#F0F2F4'}; + color: ${props => (props.variant === 'default' ? '#222222' : '#FFFFFF')}; +`; + +export const SmoothSecondaryButton = styled(SmoothButton)` + ${buttonBase} + justify-content: center; + border-radius: 9999px; + font-size: 19px; + background-color: #ff3f40; + color: #ffffff; +`; + +export const HeaderWrapper = styled.header` + margin-top: 21px; +`; + +export const HeaderTitle = styled.h2` + margin-top: 10px; + font-weight: 600; + color: #222226f3; + font-size: 22px; +`; + +export const HeaderDescription = styled.p` + margin-top: 12px; + font-weight: 500; + color: #33333e; + font-size: 17px; + line-height: 24px; +`; + +export const List = styled.ul` + margin-top: 24px; + padding-top: 24px; + border-top: 1px solid #f5f5f5; + display: flex; + flex-direction: column; + gap: 16px; +`; + +export const ListItem = styled.li` + display: flex; + align-items: center; + gap: 12px; + font-weight: 600; + color: #999999; + font-size: 15px; +`; + +export const ButtonGroup = styled.div` + margin-top: 20px; + display: flex; + gap: 16px; +`; + +export const DefaultViewHeader = styled.header` + display: flex; + height: 72px; + align-items: center; + padding-left: 8px; + margin-bottom: 16px; +`; + +export const DefaultViewTitle = styled.h2` + font-weight: 600; + color: #222226f3; + font-size: 19px; +`; + +export const ButtonContainer = styled.div` + display: flex; + flex-direction: column; + gap: 12px; +`; + +export const DangerButton = styled.button` + ${buttonBase} + background-color: #fff0f0; + color: #ff3f40; +`; diff --git a/services/ahhachul.com/src/components/common/comment/commentInput/CommentInput.component.tsx b/services/ahhachul.com/src/components/common/comment/commentInput/CommentInput.component.tsx new file mode 100644 index 000000000..e09628956 --- /dev/null +++ b/services/ahhachul.com/src/components/common/comment/commentInput/CommentInput.component.tsx @@ -0,0 +1,174 @@ +import React, { useState } from 'react'; + +import { LexicalComposer } from '@lexical/react/LexicalComposer'; +import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; +import { ContentEditable } from '@lexical/react/LexicalContentEditable'; +import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary'; +import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin'; +import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'; +import { $getRoot, type EditorState } from 'lexical'; + +import { UiComponent } from '@/components'; +import { useAuth } from '@/contexts'; + +import * as S from './CommentInput.styled'; + +import Placeholder from '../../editor/placeholder/Placeholder.component'; +import { OnChangePlugin } from '../../editor/plugins'; + +interface CommentInputProps { + disabled?: boolean; + placeholder?: string; + initialState?: string; + shouldFocusOnMount?: boolean; + showIsPrivateBtn?: boolean; + actionLabel?: string; + disablePrivateCheck?: boolean; + onSubmit: ({ isPrivate, comment }: { isPrivate: boolean; comment: string }) => void; +} + +const CommentInput = React.memo( + ({ + placeholder, + initialState, + shouldFocusOnMount, + showIsPrivateBtn, + onSubmit, + actionLabel = '๋“ฑ๋ก', + disabled = false, + disablePrivateCheck = false, + }: CommentInputProps) => { + const initialConfig = { + namespace: 'commentEditor', + onError(error: Error) { + console.error(error); + }, + }; + + const [comment, setComment] = useState(''); + const [isPrivate, setIsPrivate] = useState(false); + + const onChangeEditorContent = (editorState: EditorState | null) => { + if (editorState) { + setComment(JSON.stringify(editorState.toJSON())); + } + }; + + return ( + + + + } + placeholder={} + ErrorBoundary={LexicalErrorBoundary} + /> + + + + + + + ); + }, +); + +const SubmitComment = ({ + comment, + isPrivate, + setIsPrivate, + showIsPrivateBtn, + actionLabel, + disablePrivateCheck, + onSubmit, +}: { + comment: string; + isPrivate: boolean; + setIsPrivate: React.Dispatch>; + showIsPrivateBtn?: boolean; + disablePrivateCheck?: boolean; + actionLabel?: string; + onSubmit: ({ isPrivate, comment }: { isPrivate: boolean; comment: string }) => void; +}) => { + const { + authService: { isAuthenticated }, + } = useAuth(); + const [editor] = useLexicalComposerContext(); + + const clear = () => { + editor.update(() => { + const root = $getRoot(); + root.clear(); + }); + + setTimeout(() => { + const editorElement = document.querySelector('[contenteditable="true"]'); + if (editorElement instanceof HTMLElement) { + editorElement.blur(); + } + }, 0); + }; + + const handleSubmit = () => { + if (!isAuthenticated) { + alert('๋กœ๊ทธ์ธ ํ›„ ์ด์šฉํ•ด์ฃผ์„ธ์š”.'); + return; + } + + onSubmit({ + isPrivate: showIsPrivateBtn ? isPrivate : false, + comment, + }); + clear(); + }; + + return ( + + {showIsPrivateBtn && ( + { + if (disablePrivateCheck) { + return; + } + + setIsPrivate(e.target.checked); + editor.focus(); + }} + /> + )} + + + + + + ); +}; + +CommentInput.displayName = 'CommentInput'; + +export default CommentInput; diff --git a/services/ahhachul.com/src/components/common/comment/commentInput/CommentInput.styled.tsx b/services/ahhachul.com/src/components/common/comment/commentInput/CommentInput.styled.tsx new file mode 100644 index 000000000..d6fc93a9c --- /dev/null +++ b/services/ahhachul.com/src/components/common/comment/commentInput/CommentInput.styled.tsx @@ -0,0 +1,115 @@ +import { css, Theme } from '@emotion/react'; +import styled from '@emotion/styled'; + +export const contentEditableCss = (disalbed: boolean) => (theme: Theme) => css` + ${theme.fonts.bodyLargeSemi}; + height: 100%; + width: 100%; + border: 1px solid ${disalbed ? theme.colors.gray[20] : theme.colors.gray[50]}; + border-radius: 5px; + padding: 12px 16px; + overflow: hidden; + text-wrap: wrap; + color: ${theme.colors.gray[90]}; + background-color: ${disalbed ? theme.colors.gray[20] : theme.colors.white}; + + &:focus { + outline: none; + } +`; + +export const EditorContainer = styled.div` + position: relative; +`; + +export const Container = styled.section` + position: fixed; + left: 0; + bottom: 0; + display: flex; + flex-direction: column; + width: 100%; + border-top: 1px solid #eaecf1; + filter: drop-shadow(0px -1px 12px rgba(0, 0, 0, 0.04)); + + & > #editor-container { + padding: 12px 16px; + background-color: white; + + & > div { + border: 0; + padding: 0; + background-color: white; + max-height: 130px !important; + padding-bottom: 12px; + border-bottom: 1px solid #eaecf1; + border-radius: 0; + } + + & > pre { + left: 16px; + top: 13px; + } + } +`; + +export const SubmitBox = styled.div<{ showIsPrivateBtn?: boolean }>` + ${({ showIsPrivateBtn }) => css` + width: 100%; + margin: 0 auto; + background: #fff; + padding: 12px 16px; + padding-top: 0; + border-radius: 6px; + overflow: hidden; + display: flex; + align-items: center; + justify-content: ${showIsPrivateBtn ? 'space-between' : 'flex-end'}; + padding-bottom: 32px; + `} +`; + +export const ButtonGroup = styled.div` + display: flex; + align-items: center; + gap: 8px; + + & > button:first-of-type { + border: 0; + display: flex; + align-items: center; + justify-content: center; + background: none; + padding: 5px 8px; + color: #33333e; + font-size: 12px; + cursor: pointer; + border: 1px solid #dcdee7; + border-radius: 3px; + background-color: #fff; + + &:disabled { + opacity: 0.5; + background-color: rgb(101, 103, 107); + } + } + + & > button:last-of-type { + border: 0; + display: flex; + align-items: center; + justify-content: center; + background: none; + padding: 5px 8px; + color: #fff; + font-size: 12px; + cursor: pointer; + border-radius: 3px; + background-color: #2acf6c; + + &:disabled { + opacity: 0.5; + background-color: rgb(101, 103, 107); + } + } +`; diff --git a/services/ahhachul.com/src/components/common/comment/commentListItem/CommentListItem.component.tsx b/services/ahhachul.com/src/components/common/comment/commentListItem/CommentListItem.component.tsx index 89da15bc6..e32823342 100644 --- a/services/ahhachul.com/src/components/common/comment/commentListItem/CommentListItem.component.tsx +++ b/services/ahhachul.com/src/components/common/comment/commentListItem/CommentListItem.component.tsx @@ -1,32 +1,104 @@ +import { useActivity } from '@stackflow/react'; + import { formatDateTime } from '@ahhachul/utils'; -import { EllipsisIcon } from '@/assets/icons/system'; import { UiComponent } from '@/components'; +import { useUser } from '@/hooks/domain'; +import { useFlow } from '@/stackflow'; import type { Comment } from '@/types'; import * as S from './CommentListItem.styled'; +import { CommentDropEllipsis } from '../commentActions/CommentActions.component'; + interface CommentCardProps { comment: Comment; asChild?: boolean; + servicePath?: string; + queryKey?: readonly unknown[]; + isArticleAuthor?: boolean; } -const Comment = ({ comment, asChild = false }: CommentCardProps) => { +const Comment = ({ + comment, + asChild = false, + servicePath, + queryKey, + isArticleAuthor, +}: CommentCardProps) => { + const { push } = useFlow(); + const activity = useActivity(); + + const { user } = useUser(); + const isAuthor = user?.memberId === +comment.createdBy; + const isSuper = (comment.isPrivate && isAuthor) || (comment.isPrivate && isArticleAuthor); + return ( - + - {comment.writer} - + + {comment.writer} + {((comment.isPrivate && isAuthor) || (comment.isPrivate && isArticleAuthor)) && ( + (๋น„๊ณต๊ฐœ) + )} + + {comment.isPrivate && isSuper && queryKey && comment.status === 'CREATED' && ( + + )} + {!comment.isPrivate && queryKey && comment.status === 'CREATED' && ( + + )} - {comment.status === 'CREATED' ? ( + {comment.isPrivate && !isAuthor && !isArticleAuthor ? ( + ๋น„๊ณต๊ฐœ ๋Œ“๊ธ€์ž…๋‹ˆ๋‹ค. + ) : isSuper ? ( + + ) : comment.status === 'CREATED' ? ( ) : ( ์‚ญ์ œ๋œ ๋Œ“๊ธ€์ž…๋‹ˆ๋‹ค. )} {formatDateTime(comment.createdAt, { format: 'short' })} - ๋‹ต๊ธ€ ๋‹ฌ๊ธฐ + {comment.isPrivate && isSuper && queryKey && servicePath && !asChild && ( + { + push('NewCommentReplyPage', { + commentId: comment.id, + id: +activity.params.id!, + queryKey, + servicePath, + }); + }} + > + ๋‹ต๊ธ€ ๋‹ฌ๊ธฐ + + )} + {!comment.isPrivate && queryKey && servicePath && !asChild && ( + { + push('NewCommentReplyPage', { + commentId: comment.id, + id: +activity.params.id!, + queryKey, + servicePath, + }); + }} + > + ๋‹ต๊ธ€ ๋‹ฌ๊ธฐ + + )} ); }; diff --git a/services/ahhachul.com/src/components/common/comment/commentListItem/CommentListItem.styled.tsx b/services/ahhachul.com/src/components/common/comment/commentListItem/CommentListItem.styled.tsx index afb44c497..6366be9c3 100644 --- a/services/ahhachul.com/src/components/common/comment/commentListItem/CommentListItem.styled.tsx +++ b/services/ahhachul.com/src/components/common/comment/commentListItem/CommentListItem.styled.tsx @@ -6,7 +6,7 @@ export const CommentWrapper = styled.div<{ asChild?: boolean }>` flex-direction: column; border-bottom: 1px solid ${({ theme }) => theme.colors.gray[20]}; background-color: ${({ theme }) => theme.colors.gray[10]}; - padding: 16px 20px; + padding: 14px 20px; ${({ asChild }) => asChild && css` @@ -18,26 +18,27 @@ export const HeaderWrapper = styled.div` display: flex; align-items: center; justify-content: space-between; - padding-bottom: 8px; + padding-bottom: 4px; `; export const WriterName = styled.span` ${({ theme }) => css` color: ${theme.colors.gray[90]}; - font-size: 13px; + font-size: 14px; + font-weight: 600; `} `; export const ContentWrapper = styled.div` display: flex; flex-direction: column; - gap: 12px; - padding-bottom: 20px; + gap: 6px; + padding-bottom: 8px; `; export const DeletedComment = styled.div` ${({ theme }) => css` - ${theme.fonts.bodyLargeSemi}; + ${theme.fonts.bodyMedium}; color: ${theme.colors.gray[90]}; `} `; @@ -51,7 +52,7 @@ export const DateText = styled.span` export const ReplyButton = styled.button` ${({ theme }) => css` - ${theme.fonts.labelMedium}; + ${theme.fonts.bodySmall}; color: ${theme.colors.gray[90]}; width: max-content; `} @@ -60,8 +61,13 @@ export const ReplyButton = styled.button` export const readonlyEditorCss = css` padding: 0; - & > div { + & > div > div { padding: 0; border: none; + background-color: #fcfcfc; + + & > p { + line-height: 150%; + } } `; diff --git a/services/ahhachul.com/src/components/common/comment/emptyCommentList/EmptyCommentList.styled.tsx b/services/ahhachul.com/src/components/common/comment/emptyCommentList/EmptyCommentList.styled.tsx index e9b87e749..0adeedc69 100644 --- a/services/ahhachul.com/src/components/common/comment/emptyCommentList/EmptyCommentList.styled.tsx +++ b/services/ahhachul.com/src/components/common/comment/emptyCommentList/EmptyCommentList.styled.tsx @@ -15,10 +15,10 @@ export const Desc = styled.div` display: flex; flex-direction: column; align-items: center; - gap: 6px; + gap: 2px; & > p { - ${theme.fonts.titleMedium}; + ${theme.fonts.bodyMedium}; color: ${theme.colors.gray[80]}; } `} diff --git a/services/ahhachul.com/src/components/common/comment/error/ErrorCommentList.component.tsx b/services/ahhachul.com/src/components/common/comment/error/ErrorCommentList.component.tsx index cac9a8a06..f69b03627 100644 --- a/services/ahhachul.com/src/components/common/comment/error/ErrorCommentList.component.tsx +++ b/services/ahhachul.com/src/components/common/comment/error/ErrorCommentList.component.tsx @@ -1,7 +1,6 @@ import type { AxiosError } from 'axios'; import { ErrorGraphic } from '@/assets/graphics'; -import { WarningIcon } from '@/assets/icons/system'; import * as S from './ErrorCommentList.styled'; @@ -17,7 +16,6 @@ const ErrorCommentList = ({ reset }: ErrorCommentListProps) => { -

์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์‹œ ์‹œ๋„ diff --git a/services/ahhachul.com/src/components/common/comment/error/ErrorCommentList.styled.tsx b/services/ahhachul.com/src/components/common/comment/error/ErrorCommentList.styled.tsx index 6ae5236f3..80661dee9 100644 --- a/services/ahhachul.com/src/components/common/comment/error/ErrorCommentList.styled.tsx +++ b/services/ahhachul.com/src/components/common/comment/error/ErrorCommentList.styled.tsx @@ -17,7 +17,7 @@ export const Desc = styled.div` gap: 4px; & > p { - ${theme.fonts.titleMedium}; + ${theme.fonts.bodyMedium}; color: ${theme.colors.gray[80]}; } `} diff --git a/services/ahhachul.com/src/components/common/comment/index.ts b/services/ahhachul.com/src/components/common/comment/index.ts index 360b420e4..c8832436a 100644 --- a/services/ahhachul.com/src/components/common/comment/index.ts +++ b/services/ahhachul.com/src/components/common/comment/index.ts @@ -2,3 +2,4 @@ export { default as BaseCommentList } from './CommentList.component'; export { default as ErrorCommentList } from './error/ErrorCommentList.component'; export { default as CommentListSkeleton } from './skeleton/CommentList.skeleton'; export { default as EmptyCommentList } from './emptyCommentList/EmptyCommentList.component'; +export { default as CommentInput } from './commentInput/CommentInput.component'; diff --git a/services/ahhachul.com/src/components/common/editor/Editor.component.tsx b/services/ahhachul.com/src/components/common/editor/Editor.component.tsx index f610c6240..cf339d6e9 100644 --- a/services/ahhachul.com/src/components/common/editor/Editor.component.tsx +++ b/services/ahhachul.com/src/components/common/editor/Editor.component.tsx @@ -11,12 +11,9 @@ import Mic from './mic/Mic.component'; import Placeholder from './placeholder/Placeholder.component'; import { OnChangePlugin, SpeechToTextPlugin } from './plugins'; -function onError(error: Error) { - console.error(error); -} - type Props = { showMic?: boolean; + disabled?: boolean; hasError?: boolean; readonly?: boolean; placeholder?: string; @@ -30,24 +27,31 @@ const Editor = ({ readonly = false, placeholder = '', initialState = '', + disabled = false, overrideCss, onChange, }: Props) => { const initialConfig = { namespace: 'plainEditor', - onError, + onError(error: Error) { + console.error(error); + }, }; return ( - + } + contentEditable={} placeholder={} ErrorBoundary={LexicalErrorBoundary} /> - + {showMic && ( <> diff --git a/services/ahhachul.com/src/components/common/editor/Editor.styled.tsx b/services/ahhachul.com/src/components/common/editor/Editor.styled.tsx index 7a42e3e8b..71dfccbba 100644 --- a/services/ahhachul.com/src/components/common/editor/Editor.styled.tsx +++ b/services/ahhachul.com/src/components/common/editor/Editor.styled.tsx @@ -5,16 +5,17 @@ export const EditorContainer = styled.div` position: relative; `; -export const contentEditableCss = (theme: Theme) => css` +export const contentEditableCss = (disalbed: boolean) => (theme: Theme) => css` ${theme.fonts.bodyLargeSemi}; height: 100%; width: 100%; - border: 1px solid ${theme.colors.gray[50]}; + border: 1px solid ${disalbed ? theme.colors.gray[20] : theme.colors.gray[50]}; border-radius: 5px; padding: 12px; overflow: hidden; text-wrap: wrap; color: ${theme.colors.gray[90]}; + background-color: ${disalbed ? theme.colors.gray[20] : theme.colors.white}; &:focus { outline: none; diff --git a/services/ahhachul.com/src/components/common/editor/plugins/hooks/useReport.ts b/services/ahhachul.com/src/components/common/editor/plugins/hooks/useReport.ts index d09724d3e..5051fd3ca 100644 --- a/services/ahhachul.com/src/components/common/editor/plugins/hooks/useReport.ts +++ b/services/ahhachul.com/src/components/common/editor/plugins/hooks/useReport.ts @@ -43,7 +43,6 @@ export function useReport(): (arg0: string) => ReturnType { return useCallback( content => { // eslint-disable-next-line no-console - console.log(content); const element = getElement(); if (timer.current !== null) { clearTimeout(timer.current); diff --git a/services/ahhachul.com/src/components/common/editor/plugins/onChangePlugin/OnChangePlugin.component.tsx b/services/ahhachul.com/src/components/common/editor/plugins/onChangePlugin/OnChangePlugin.component.tsx index e43ef43c9..88f23ee37 100644 --- a/services/ahhachul.com/src/components/common/editor/plugins/onChangePlugin/OnChangePlugin.component.tsx +++ b/services/ahhachul.com/src/components/common/editor/plugins/onChangePlugin/OnChangePlugin.component.tsx @@ -6,6 +6,7 @@ import { $getRoot, EditorState, ElementNode } from 'lexical'; type Props = { readonly?: boolean; initialState?: string; + shouldFocusOnMount?: boolean; onChange?: (editorState: EditorState | null) => void; }; @@ -21,7 +22,7 @@ const isEditorEmpty = () => { return false; }; -export function OnChangePlugin({ readonly, initialState, onChange }: Props) { +export function OnChangePlugin({ readonly, initialState, shouldFocusOnMount, onChange }: Props) { const [editor] = useLexicalComposerContext(); useEffect(() => { @@ -37,7 +38,16 @@ export function OnChangePlugin({ readonly, initialState, onChange }: Props) { editor.setEditorState(content); }); } - }, [readonly, initialState]); + + if (shouldFocusOnMount) { + setTimeout(() => { + const editorElement = document.querySelector('[contenteditable="true"]'); + if (editorElement instanceof HTMLElement) { + editorElement.click(); + } + }, 550); + } + }, [readonly, initialState, shouldFocusOnMount]); useEffect(() => { return editor.registerUpdateListener(({ editorState }) => { diff --git a/services/ahhachul.com/src/components/common/filter/drawerFilter/DrawerFilter.component.tsx b/services/ahhachul.com/src/components/common/filter/drawerFilter/DrawerFilter.component.tsx index 0fe76c4a7..a366b6d16 100644 --- a/services/ahhachul.com/src/components/common/filter/drawerFilter/DrawerFilter.component.tsx +++ b/services/ahhachul.com/src/components/common/filter/drawerFilter/DrawerFilter.component.tsx @@ -32,8 +32,8 @@ const DrawerFilter: React.FC = ({ label, drawerTitle }) => { - - + + @@ -48,11 +48,13 @@ const DrawerFilter: React.FC = ({ label, drawerTitle }) => { - + - + + ๊ฒ€์ƒ‰์ค‘... + - + ); diff --git a/services/ahhachul.com/src/components/common/filter/drawerFilter/DrawerFilter.styled.tsx b/services/ahhachul.com/src/components/common/filter/drawerFilter/DrawerFilter.styled.tsx index a60b9496c..8b120f067 100644 --- a/services/ahhachul.com/src/components/common/filter/drawerFilter/DrawerFilter.styled.tsx +++ b/services/ahhachul.com/src/components/common/filter/drawerFilter/DrawerFilter.styled.tsx @@ -21,7 +21,7 @@ export const FilterButton = styled.button<{ isActive: boolean }>` } `; -export const Overlay = styled(Drawer.Overlay)` +export const overlay = css` position: fixed; top: 0; right: 0; @@ -30,18 +30,17 @@ export const Overlay = styled(Drawer.Overlay)` background-color: rgba(0, 0, 0, 0.4); `; -export const DrawerContent = styled(Drawer.Content)` - z-index: ${({ theme }) => theme.zIndex.drawer}; +export const drawerContainer = css` + z-index: 999999999; display: flex; flex-direction: column; border-top-left-radius: 10px; border-top-right-radius: 10px; - height: max-content; + max-height: 96%; position: fixed; bottom: 0; left: 0; right: 0; - box-shadow: 0px -10px 16px 0px rgba(0, 0, 0, 0.17); `; export const ContentWrapper = styled.div` @@ -108,7 +107,7 @@ export const SearchInput = styled.input` color: ${theme.colors.gray[90]}; background-color: ${theme.colors.gray[20]}; padding: 0 12px 0 30px; - font-size: 16px; + font-size: 14px; caret-color: ${theme.colors['key-color']}; border-radius: 12px; @@ -126,6 +125,12 @@ export const SearchInput = styled.input` export const ContentArea = styled.div` background-color: ${({ theme }) => theme.colors.gray[20]}; - height: 500px; + height: 360px; border-radius: 12px; + padding: 24px; + + & > span { + ${({ theme }) => theme.fonts.labelMedium}; + color: ${({ theme }) => theme.colors.gray[90]}; + } `; diff --git a/services/ahhachul.com/src/components/common/float/atoms/floatBtn/FloatButton.component.tsx b/services/ahhachul.com/src/components/common/float/atoms/floatBtn/FloatButton.component.tsx index 5c4d64abd..1120f7f17 100644 --- a/services/ahhachul.com/src/components/common/float/atoms/floatBtn/FloatButton.component.tsx +++ b/services/ahhachul.com/src/components/common/float/atoms/floatBtn/FloatButton.component.tsx @@ -4,11 +4,12 @@ import * as S from './FloatButton.styled'; interface FloatButtonProps { onClick: () => void; + className?: string; } -const FloatButton = ({ onClick, children }: PropsWithChildren) => { +const FloatButton = ({ onClick, className, children }: PropsWithChildren) => { return ( - + {children} ); diff --git a/services/ahhachul.com/src/components/common/float/atoms/floatBtn/FloatButton.styled.tsx b/services/ahhachul.com/src/components/common/float/atoms/floatBtn/FloatButton.styled.tsx index e81d88c34..0aa31c267 100644 --- a/services/ahhachul.com/src/components/common/float/atoms/floatBtn/FloatButton.styled.tsx +++ b/services/ahhachul.com/src/components/common/float/atoms/floatBtn/FloatButton.styled.tsx @@ -1,13 +1,15 @@ import { css } from '@emotion/react'; import styled from '@emotion/styled'; +import { isWebView } from '@/constants'; + export const FloatButton = styled.button` ${({ theme }) => css` ${theme.fonts.bodyLarge}; position: fixed; right: 20px; - bottom: 80px; + bottom: ${isWebView() ? '112px' : '80px'}; width: max-content; height: 44px; diff --git a/services/ahhachul.com/src/components/common/float/molecules/newBtn/newBtn.component.tsx b/services/ahhachul.com/src/components/common/float/molecules/newBtn/newBtn.component.tsx index d3a4809ce..98c33bfbd 100644 --- a/services/ahhachul.com/src/components/common/float/molecules/newBtn/newBtn.component.tsx +++ b/services/ahhachul.com/src/components/common/float/molecules/newBtn/newBtn.component.tsx @@ -1,25 +1,46 @@ -import { PlusIcon } from '@/assets/icons/system'; +import { ListIcon, PlusIcon } from '@/assets/icons/system'; import { UiComponent } from '@/components'; import { useAuth } from '@/contexts'; import { type TypeActivities, useFlow } from '@/stackflow'; import type { KeyOf } from '@/types'; +type NewBtnType = 'new' | 'list'; + interface NewBtnProps { activityName: KeyOf; + label?: string; + type?: NewBtnType; + replace?: boolean; + checkAuth?: boolean; + className?: string; } -const NewBtn = ({ activityName }: NewBtnProps) => { - const { push } = useFlow(); +const NewBtn = ({ + activityName, + label = '๊ธ€์“ฐ๊ธฐ', + type = 'new', + replace = false, + checkAuth = true, + className, +}: NewBtnProps) => { + const { push, replace: replacePage } = useFlow(); const { authService } = useAuth(); const onClick = () => { - push(authService.isAuthenticated ? activityName : 'SignInPage', {}); + const action = replace ? replacePage : push; + action( + !checkAuth ? activityName : authService.isAuthenticated ? activityName : 'SignInPage', + {}, + { + animate: !replace, + }, + ); }; return ( - - - ๊ธ€์“ฐ๊ธฐ + + {type === 'new' ? : } + {label} ); }; diff --git a/services/ahhachul.com/src/components/common/form/molecules/error/FormErrorMessage.styled.tsx b/services/ahhachul.com/src/components/common/form/molecules/error/FormErrorMessage.styled.tsx index 0e15ca6f4..7eb636279 100644 --- a/services/ahhachul.com/src/components/common/form/molecules/error/FormErrorMessage.styled.tsx +++ b/services/ahhachul.com/src/components/common/form/molecules/error/FormErrorMessage.styled.tsx @@ -8,7 +8,7 @@ export const ErrorMessage = styled.div` color: ${({ theme }) => theme.colors.red}; gap: 6px; - & > div > svg > path { + & > svg > path { fill: #e02020; stroke: #ffffff; diff --git a/services/ahhachul.com/src/components/common/form/organisms/select/Select.component.tsx b/services/ahhachul.com/src/components/common/form/organisms/select/Select.component.tsx index 193c6f5c6..bd4d02b98 100644 --- a/services/ahhachul.com/src/components/common/form/organisms/select/Select.component.tsx +++ b/services/ahhachul.com/src/components/common/form/organisms/select/Select.component.tsx @@ -2,6 +2,8 @@ import { Controller, Path, RegisterOptions, FieldValues, useFormContext } from ' import { FormComponent } from '@/components'; +import * as S from './Select.styled'; + import { SelectMolecules } from '../../molecules'; interface SelectFieldProps { @@ -43,7 +45,7 @@ const SelectField = ({ /> )} /> - + ); }; diff --git a/services/ahhachul.com/src/components/common/form/organisms/select/Select.styled.tsx b/services/ahhachul.com/src/components/common/form/organisms/select/Select.styled.tsx new file mode 100644 index 000000000..6caeb0712 --- /dev/null +++ b/services/ahhachul.com/src/components/common/form/organisms/select/Select.styled.tsx @@ -0,0 +1,6 @@ +import { css } from '@emotion/react'; + +export const errorStyle = css` + margin-top: 12px; + padding-left: 20px; +`; diff --git a/services/ahhachul.com/src/components/common/gnb/Gnb.constant.tsx b/services/ahhachul.com/src/components/common/gnb/Gnb.constant.tsx index 9227dd18c..6e9dc893f 100644 --- a/services/ahhachul.com/src/components/common/gnb/Gnb.constant.tsx +++ b/services/ahhachul.com/src/components/common/gnb/Gnb.constant.tsx @@ -15,31 +15,31 @@ import type { NavItem } from './navItem/NaItem.type'; export const GNBList: NavItem[] = [ { - href: 'HomePage', + href: ['HomePage'], icon: , activeIcon: , label: 'ํ™ˆ', }, { - href: 'CommunityPage', + href: ['CommunityPage'], icon: , activeIcon: , label: '์ปค๋ฎค๋‹ˆํ‹ฐ', }, { - href: 'LostFoundPage', + href: ['LostFoundPage'], icon: , activeIcon: , label: '์œ ์‹ค๋ฌผ', }, { - href: 'ComplaintPage', + href: ['ComplaintPage', 'ComplaintPanelPage'], icon: , activeIcon: , label: '๋ฏผ์›', }, { - href: 'MyPage', + href: ['MyPage'], icon: , activeIcon: , label: '๋งˆ์ด', diff --git a/services/ahhachul.com/src/components/common/gnb/Gnb.styled.ts b/services/ahhachul.com/src/components/common/gnb/Gnb.styled.ts index a83b16e60..3da46efbe 100644 --- a/services/ahhachul.com/src/components/common/gnb/Gnb.styled.ts +++ b/services/ahhachul.com/src/components/common/gnb/Gnb.styled.ts @@ -9,7 +9,6 @@ export const Navbar = styled.nav` display: grid; grid-template-columns: repeat(5, 1fr); width: 100%; - height: 60px; background: ${({ theme }) => theme.colors.white}; border-top: 1px solid ${({ theme }) => theme.colors.gray[20]}; z-index: ${({ theme }) => theme.zIndex.navbar}; diff --git a/services/ahhachul.com/src/components/common/gnb/navItem/NaItem.type.ts b/services/ahhachul.com/src/components/common/gnb/navItem/NaItem.type.ts index 76a7b81fe..d04fa4cc6 100644 --- a/services/ahhachul.com/src/components/common/gnb/navItem/NaItem.type.ts +++ b/services/ahhachul.com/src/components/common/gnb/navItem/NaItem.type.ts @@ -2,7 +2,7 @@ import type { TypeActivities } from '@/stackflow'; import type { KeyOf } from '@/types/common'; export interface NavItem { - href: KeyOf; + href: KeyOf[]; label: string; icon: React.ReactNode; activeIcon: React.ReactNode; diff --git a/services/ahhachul.com/src/components/common/gnb/navItem/NavItem.hook.ts b/services/ahhachul.com/src/components/common/gnb/navItem/NavItem.hook.ts index 2016852ce..5ed3232d1 100644 --- a/services/ahhachul.com/src/components/common/gnb/navItem/NavItem.hook.ts +++ b/services/ahhachul.com/src/components/common/gnb/navItem/NavItem.hook.ts @@ -1,4 +1,4 @@ -import { useNativeBridge } from '@/contexts'; +import { useAuth, useNativeBridge } from '@/contexts'; import { useFlow, useActivity } from '@/stackflow'; import type { NavItem } from './NaItem.type'; @@ -11,11 +11,15 @@ export const useNavItem = ({ handleScrollToTop?: VoidFunction; }) => { const activity = useActivity(); - const isActive = activity.name === item.href; + const isActive = item.href.includes(activity.name as NavItem['href'][0]); - const { replace } = useFlow(); + const { push, replace } = useFlow(); const { bridge, isBridgeInitialized } = useNativeBridge(); + const { + authService: { isAuthenticated }, + } = useAuth(); + const handleTabClick = () => { if (isActive) { handleScrollToTop?.(); @@ -26,7 +30,12 @@ export const useNavItem = ({ bridge.send.haptic(); } - replace(item.href, {}, { animate: false }); + if (item.href[0] === 'MyPage' && !isAuthenticated) { + push('SignInPage', {}); + return; + } + + replace(item.href[0], {}, { animate: false }); }; return { diff --git a/services/ahhachul.com/src/components/common/gnb/navItem/NavItem.styled.ts b/services/ahhachul.com/src/components/common/gnb/navItem/NavItem.styled.ts index 52f1b6f32..74545559e 100644 --- a/services/ahhachul.com/src/components/common/gnb/navItem/NavItem.styled.ts +++ b/services/ahhachul.com/src/components/common/gnb/navItem/NavItem.styled.ts @@ -15,7 +15,7 @@ export const NavItemButton = styled.button` align-items: center; justify-content: center; gap: 2px; - height: 100%; + height: 60px; padding: 2px; text-align: center; text-decoration: none; diff --git a/services/ahhachul.com/src/components/common/header/HeaderBrand.component.tsx b/services/ahhachul.com/src/components/common/header/HeaderBrand.component.tsx index ba32f4338..7f8dd5c8e 100644 --- a/services/ahhachul.com/src/components/common/header/HeaderBrand.component.tsx +++ b/services/ahhachul.com/src/components/common/header/HeaderBrand.component.tsx @@ -5,7 +5,13 @@ import * as S from './HeaderBrand.styled'; const HeaderBrand = () => { return ( - + ); diff --git a/services/ahhachul.com/src/components/common/index.ts b/services/ahhachul.com/src/components/common/index.ts index f9769e29e..e78200386 100644 --- a/services/ahhachul.com/src/components/common/index.ts +++ b/services/ahhachul.com/src/components/common/index.ts @@ -9,7 +9,8 @@ export * from './portal'; export * from './filter'; export * from './loading'; export * from './comment'; -export * from './actionSheet'; +export * from './checkbox'; +export * from './postDropEllipsis'; export * from './searchInput'; export * from './animatePortal'; export * from './readOnlyEditor'; @@ -24,3 +25,6 @@ export * from './imageCarouselModal'; export * from './skeleton'; export * from './iosBottomPadding'; + +export * from './toast'; +export * from './maintain'; diff --git a/services/ahhachul.com/src/components/common/iosBottomPadding/IOSBottomPadding.component.tsx b/services/ahhachul.com/src/components/common/iosBottomPadding/IOSBottomPadding.component.tsx index faf226253..97ff01c4a 100644 --- a/services/ahhachul.com/src/components/common/iosBottomPadding/IOSBottomPadding.component.tsx +++ b/services/ahhachul.com/src/components/common/iosBottomPadding/IOSBottomPadding.component.tsx @@ -1,9 +1,9 @@ -import { isIOS } from '@/constants'; +import { isWebView } from '@/constants'; import * as S from './IOSBottomPadding.styled'; export const IOSBottomPadding = () => { - if (!isIOS()) return null; + if (!isWebView()) return null; return ; }; diff --git a/services/ahhachul.com/src/components/common/list/emptyList/EmptyList.styled.tsx b/services/ahhachul.com/src/components/common/list/emptyList/EmptyList.styled.tsx index a98a8f924..7b0efdf39 100644 --- a/services/ahhachul.com/src/components/common/list/emptyList/EmptyList.styled.tsx +++ b/services/ahhachul.com/src/components/common/list/emptyList/EmptyList.styled.tsx @@ -12,7 +12,7 @@ export const EmptyContainer = styled.div` padding-top: 184px; & > p { - ${theme.fonts.titleMedium}; + ${theme.fonts.bodyMedium}; color: ${theme.colors.gray[80]}; } `} diff --git a/services/ahhachul.com/src/components/common/list/listItem/ListItem.component.tsx b/services/ahhachul.com/src/components/common/list/listItem/ListItem.component.tsx index 2e7b28453..aa3f678ea 100644 --- a/services/ahhachul.com/src/components/common/list/listItem/ListItem.component.tsx +++ b/services/ahhachul.com/src/components/common/list/listItem/ListItem.component.tsx @@ -28,7 +28,7 @@ const Post = ({ post }: PostProps) => { {post.content} )} - {post?.imageUrl && ( + {(post?.imageUrl || post.image) && ( { - {subwayIconMap.get(post.subwayLineId)} - + {post.subwayLineId && ( + <> + {subwayIconMap.get(post.subwayLineId)} + + + )} {post.writer || '๋กœ์ŠคํŠธ 112'} {formatDateTime(post.createdAt, { format: 'relative' })} diff --git a/services/ahhachul.com/src/components/common/list/listItem/ListItem.styled.tsx b/services/ahhachul.com/src/components/common/list/listItem/ListItem.styled.tsx index 8c2f5f40f..5e28f584c 100644 --- a/services/ahhachul.com/src/components/common/list/listItem/ListItem.styled.tsx +++ b/services/ahhachul.com/src/components/common/list/listItem/ListItem.styled.tsx @@ -7,26 +7,25 @@ import styled from '@emotion/styled'; import { DotIcon } from '@/assets/icons/system'; export const Article = styled.article` - padding: 20px 0; + padding: 14px 0; border-bottom: 1px solid #f5f5f5; `; export const Container = styled.div` display: flex; flex-direction: column; - gap: 12px; + gap: 14px; `; export const ContentWrapper = styled.div` display: flex; - gap: 6px; + gap: 8px; `; export const TextContainer = styled.div` width: 100%; display: flex; flex-direction: column; - gap: 6px; `; export const Title = styled.div` @@ -107,7 +106,7 @@ export const lexicalContentStyle = css` & > div > div { padding: 0; border: none; - max-height: 46px; + max-height: 18px; overflow: hidden; } `; diff --git a/services/ahhachul.com/src/components/common/loading/SpinnerIcon.component.tsx b/services/ahhachul.com/src/components/common/loading/SpinnerIcon.component.tsx new file mode 100644 index 000000000..1b8f439fd --- /dev/null +++ b/services/ahhachul.com/src/components/common/loading/SpinnerIcon.component.tsx @@ -0,0 +1,28 @@ +import React from 'react'; + +interface SpinnerIconProps extends React.SVGProps { + size?: number; + className?: string; +} + +const SpinnerIcon: React.FC = ({ size = 14, className, ...props }) => { + return ( + + + + ); +}; + +export default SpinnerIcon; diff --git a/services/ahhachul.com/src/components/common/loading/index.ts b/services/ahhachul.com/src/components/common/loading/index.ts index ba4fde527..a3d9ec39c 100644 --- a/services/ahhachul.com/src/components/common/loading/index.ts +++ b/services/ahhachul.com/src/components/common/loading/index.ts @@ -1 +1,2 @@ +export { default as SpinnerIcon } from './SpinnerIcon.component'; export { default as LoadingSpinner } from './Spinner.component'; diff --git a/services/ahhachul.com/src/components/common/maintain/MaintainContent.component.tsx b/services/ahhachul.com/src/components/common/maintain/MaintainContent.component.tsx new file mode 100644 index 000000000..9c998a92c --- /dev/null +++ b/services/ahhachul.com/src/components/common/maintain/MaintainContent.component.tsx @@ -0,0 +1,108 @@ +import styled from '@emotion/styled'; + +import { useFlow } from '@/stackflow'; + +const MaintainContent = ({ actionLabel = 'ํ™ˆ์œผ๋กœ' }: { actionLabel?: string }) => { + const { pop } = useFlow(); + const handleBack = () => pop(); + + return ( + + + + + + + + + ๊ธฐ๋Šฅ ์ค€๋น„ ์ค‘์ž…๋‹ˆ๋‹ค + {'๋” ๋‚˜์€ ์„œ๋น„์Šค ์ œ๊ณต์„ ์œ„ํ•ด\nํ•ด๋‹น ๊ธฐ๋Šฅ์˜ ์—…๋ฐ์ดํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.'} + + {actionLabel} + + + + ); +}; + +const Container = styled.div` + display: flex; + flex-direction: column; + min-height: calc(100% - 58px); + background-color: white; + padding: 24px; +`; + +const ContentWrapper = styled.div` + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + margin-top: -60px; +`; + +const Title = styled.h1` + font-size: 18px; + font-weight: 600; + color: #363e52; + margin-top: 10px; + margin-bottom: 12px; + text-align: center; + line-height: 28px; + letter-spacing: -0.2px; +`; + +const Message = styled.p` + font-size: 16px; + font-weight: 500; + line-height: 26px; + letter-spacing: -0.2px; + color: #949db2; + text-align: center; + margin-bottom: 24px; + white-space: break-spaces; +`; + +const ButtonGroup = styled.div` + display: flex; + gap: 12px; + width: 100%; + max-width: 154px; + align-items: center; + justify-content: center; +`; + +const Button = styled.button` + flex: 1; + padding: 15px 0; + border-radius: 8px; + font-size: 16px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; + background-color: white; +`; + +const HomeButton = styled(Button)` + border: 1px solid #e3e5e8; + color: #3c3f44; +`; + +export default MaintainContent; diff --git a/services/ahhachul.com/src/components/common/maintain/index.ts b/services/ahhachul.com/src/components/common/maintain/index.ts new file mode 100644 index 000000000..6ee24f578 --- /dev/null +++ b/services/ahhachul.com/src/components/common/maintain/index.ts @@ -0,0 +1 @@ +export { default as MaintainContent } from './MaintainContent.component'; diff --git a/services/ahhachul.com/src/components/common/postDropEllipsis/PostDropEllipsis.component.tsx b/services/ahhachul.com/src/components/common/postDropEllipsis/PostDropEllipsis.component.tsx new file mode 100644 index 000000000..1be207c9f --- /dev/null +++ b/services/ahhachul.com/src/components/common/postDropEllipsis/PostDropEllipsis.component.tsx @@ -0,0 +1,439 @@ +import React, { useMemo, useRef, useState } from 'react'; +import useMeasure from 'react-use-measure'; + +import styled from '@emotion/styled'; +import { useQueryClient } from '@tanstack/react-query'; +import { Check } from 'lucide-react'; +// import { useQueryClient } from '@tanstack/react-query'; +import { AnimatePresence, motion } from 'motion/react'; +import { Drawer } from 'vaul'; + +import { sleep } from '@ahhachul/utils'; + +import { + CloseIcon, + DangerIcon, + FaceIDIcon, + LockIcon, + PhraseIcon, + WarningIcon, +} from '@/assets/icons/jsx/icons'; +import { MoreVerticalIcon } from '@/assets/icons/system'; +import { useUser } from '@/hooks/domain'; +import useUpdateLostFound from '@/hooks/domain/lostFound/useUpdateLostFound'; +import { communityKeys, useDeleteCommunity } from '@/services/community'; +import { complaintKeys, useDeleteComplaint } from '@/services/complaint'; +import { lostFoundKeys, useDeleteLostFound } from '@/services/lostFound'; +import { useFlow } from '@/stackflow'; +import { LostStatus } from '@/types'; + +import * as S from './PostDropEllipsis.styled'; + +export interface PostDropEllipsisProps { + isLost?: boolean; + status?: LostStatus; + articleId: string; + createdBy: number; + queryKey: readonly unknown[]; +} + +const PostDropEllipsis = ({ + isLost, + status, + articleId, + createdBy, + queryKey, +}: PostDropEllipsisProps): React.ReactElement => { + const [isOpen, setIsOpen] = useState(false); + const [view, setView] = useState('default'); + const [elementRef, bounds] = useMeasure(); + const previousHeightRef = useRef(0); + + const { user } = useUser(); + const isAuthor = user?.memberId === createdBy; + + const handleOpen = () => { + setView('default'); + setTimeout(() => { + setIsOpen(true); + }, 100); + }; + const handleClose = () => setIsOpen(false); + + const { push } = useFlow(); + const handleEdit = () => { + handleClose(); + const activityName = queryKey.includes('community') + ? 'EditCommunityPage' + : queryKey.includes('lostFound') + ? 'EditLostFoundPage' + : 'EditComplaintPage'; + + setTimeout(() => { + push(activityName, { + id: +articleId, + }); + }, 500); + }; + + const content = useMemo(() => { + switch (view) { + case 'default': + return isAuthor ? ( + + ) : ( + + ); + case 'remove': + return ( + + ); + case 'update': + return ( + + ); + } + }, [view, queryKey]); + + const opacityDuration = useMemo(() => { + const MIN_DURATION = 0.15; + const MAX_DURATION = 0.27; + + if (!previousHeightRef.current) { + previousHeightRef.current = bounds.height; + return MIN_DURATION; + } + + const heightDifference = Math.abs(bounds.height - previousHeightRef.current); + previousHeightRef.current = bounds.height; + + const duration = Math.min(Math.max(heightDifference / 500, MIN_DURATION), MAX_DURATION); + + return duration; + }, [bounds.height]); + + return ( +
+ + + + + + + + + + + + + + + + + {content} + + + + + + + +
+ ); +}; + +function Header({ + icon, + title, + description, +}: { + icon: React.ReactNode; + title: string; + description: string; +}) { + return ( + + {icon} + {title} + {description} + + ); +} + +function DefaultView({ + isLost, + status, + setView, + handleEdit, +}: { + isLost?: boolean; + status?: LostStatus; + setView: (view: string) => void; + handleEdit: () => void; +}) { + return ( + <> + + ์„ค์ • + + + {isLost && ( + setView('update')}> + + {status === 'PROGRESS' ? '์ฐพ๊ธฐ ์™„๋ฃŒ' : '์ƒํƒœ ๋ณ€๊ฒฝ'} + + )} + + + ์ˆ˜์ •ํ•˜๊ธฐ + + setView('remove')}> + + ์‚ญ์ œํ•˜๊ธฐ + + + + ); +} + +function ReportView({ handleClose }: { handleClose: () => void }) { + return ( + <> + + ์„ค์ • + + + + + ์‹ ๊ณ ํ•˜๊ธฐ + + + + ); +} + +function RemovePost({ + articleId, + queryKey, + setView, + handleClose, +}: { + articleId: string; + queryKey: readonly unknown[]; + setView: (view: string) => void; + handleClose: () => void; +}) { + const { pop } = useFlow(); + const queryClient = useQueryClient(); + + const { mutateAsync: deleteCommunity, status: deletingCommunityStatus } = useDeleteCommunity(); + const { mutateAsync: deleteLostFound, status: deletingLostFoundStatus } = useDeleteLostFound(); + const { mutateAsync: deleteComplaint, status: deletingComplaintStatus } = useDeleteComplaint(); + + const invlidationQueryKey = queryKey.includes('community') + ? communityKeys.lists() + : queryKey.includes('lostFound') + ? lostFoundKeys.lists() + : complaintKeys.lists(); + + const deleteMutate = queryKey.includes('community') + ? deleteCommunity + : queryKey.includes('lostFound') + ? deleteLostFound + : deleteComplaint; + + const status = queryKey.includes('community') + ? deletingCommunityStatus + : queryKey.includes('lostFound') + ? deletingLostFoundStatus + : deletingComplaintStatus; + + const handleDeletePost = async () => { + try { + await deleteMutate(+articleId, { + onSuccess: async () => { + await sleep(350); + handleClose(); + await sleep(200); + await queryClient.invalidateQueries({ queryKey: invlidationQueryKey }); + pop(); + }, + }); + } catch (error) { + console.error('๊ฒŒ์‹œ๋ฌผ ์‚ญ์ œ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:', error); + } + }; + + return ( +
+
+
} + title="๊ฒŒ์‹œ๊ธ€์„ ์‚ญ์ œํ•˜์‹œ๊ฒ ์–ด์š”?" + description={`์‚ญ์ œํ•˜์‹œ๋ฉด ๋ณต๊ตฌํ•  ์ˆ˜ ์—†์–ด์š”.\nํ•ด๋‹น ๊ฒŒ์‹œ๊ธ€์„ ์‚ญ์ œํ• ๊นŒ์š”?`} + /> + + setView('default')} + > + ์ทจ์†Œ + + + +
+
+ ); +} + +function UpdatePost({ + articleId, + lostStatus, + setView, + handleClose, +}: { + articleId: string; + queryKey: readonly unknown[]; + lostStatus?: LostStatus; + setView: (view: string) => void; + handleClose: () => void; +}) { + const queryClient = useQueryClient(); + + const { mutateAsync, status } = useUpdateLostFound(); + const handleUpdateLostStatus = () => { + mutateAsync( + { + articleId: +articleId, + status: lostStatus === 'COMPLETE' ? 'PROGRESS' : 'COMPLETE', + }, + { + onSuccess: () => { + handleClose(); + setTimeout(() => { + queryClient.invalidateQueries({ + queryKey: lostFoundKeys.detail(+articleId), + }); + }, 1000); + }, + }, + ); + }; + + return ( +
+
+
: } + title={lostStatus === 'COMPLETE' ? '์ฐพ์Œ ์ƒํƒœ๋ฅผ ์ทจ์†Œํ•˜์‹œ๊ฒ ์–ด์š”?' : '๋ถ„์‹ค๋ฌผ์„ ์ฐพ์œผ์…จ๋‚˜์š”?'} + description={ + lostStatus === 'COMPLETE' + ? `๋ฌผ๊ฑด์„ ๋‹ค์‹œ ๋ถ„์‹ค ์ƒํƒœ๋กœ ๋ณ€๊ฒฝํ•˜์‹œ๊ฒ ์–ด์š”?\n๊ฒŒ์‹œ๊ธ€์ด '์ฐพ์ง€ ๋ชปํ•จ' ์ƒํƒœ๋กœ ๋Œ์•„๊ฐ‘๋‹ˆ๋‹ค.` + : `๋ถ„์‹ค๋ฌผ์„ ์ฐพ์œผ์…จ๋‹ค๋ฉด ์•Œ๋ ค์ฃผ์„ธ์š”.\n๊ฒŒ์‹œ๊ธ€ ์ƒํƒœ๊ฐ€ '์ฐพ์Œ'์œผ๋กœ ๋ณ€๊ฒฝ๋ฉ๋‹ˆ๋‹ค.` + } + /> + + setView('default')}> + ์ทจ์†Œ + + + +
+
+ ); +} + +const DrawerButton = styled.button` + height: 24px; + font-weight: 500; + color: black; + transition: background-color 0.2s; + display: flex; + align-items: center; + width: 100%; + justify-content: flex-end; + &:focus-visible { + box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.5); + } +`; + +const DrawerOverlay = styled(Drawer.Overlay)` + position: fixed; + inset: 0; + z-index: 10000; + background-color: rgba(0, 0, 0, 0.3); + transition: opacity 0.2s cubic-bezier(0.165, 0.84, 0.44, 1); +`; + +const DrawerContentWrapper = styled(motion.div)` + position: fixed; + left: 16px; + right: 16px; + bottom: 16px; + z-index: 10010; + max-width: 360px; + margin-left: auto; + margin-right: auto; + overflow: hidden; + border-radius: 36px; + background-color: #ffffff; + outline: none; + transition: transform 0.2s cubic-bezier(0.165, 0.84, 0.44, 1); +`; + +const CloseButton = styled.button` + position: absolute; + top: 28px; + right: 32px; + z-index: 10; + display: flex; + height: 32px; + width: 32px; + align-items: center; + justify-content: center; + border-radius: 9999px; + background-color: #f7f8f9; + color: #949595; + transition: transform 0.2s; + &:focus { + transform: scale(0.95); + } + &:active { + transform: scale(0.75); + } +`; + +const ContentWrapper = styled.div` + padding: 10px 24px 24px; +`; + +export default PostDropEllipsis; diff --git a/services/ahhachul.com/src/components/common/postDropEllipsis/PostDropEllipsis.styled.tsx b/services/ahhachul.com/src/components/common/postDropEllipsis/PostDropEllipsis.styled.tsx new file mode 100644 index 000000000..a0db13ddb --- /dev/null +++ b/services/ahhachul.com/src/components/common/postDropEllipsis/PostDropEllipsis.styled.tsx @@ -0,0 +1,212 @@ +import { type Interpolation, type Theme, css } from '@emotion/react'; +import styled from '@emotion/styled'; + +import { SmoothButton } from '../button/SmoothButton.component'; + +export const Dim = styled.div` + ${({ theme }) => css` + position: fixed; + inset: 0; + background-color: ${theme.colors.dim}; + z-index: ${theme.zIndex.dim}; + transition: opacity 0.3s ease; + `} +`; + +export const Sheet = styled.div` + ${({ theme }) => css` + position: fixed; + bottom: 0; + left: 0; + right: 0; + z-index: ${theme.zIndex.drawer}; + display: flex; + flex-direction: column; + padding: 16px; + padding-bottom: 40px; + animation: slideIn 0.3s ease-out; + + @keyframes slideIn { + from { + transform: translateY(100%); + } + to { + transform: translateY(0); + } + } + `} +`; + +export const Container = styled.div` + ${({ theme }) => css` + border-radius: 16px; + display: flex; + flex-direction: column; + overflow: hidden; + + & > button:not(:last-of-type) { + border-bottom: 1px solid ${theme.colors.gray[20]}; + } + `} +`; + +export const ActionButton = styled.button` + ${({ theme }) => css` + ${theme.fonts.bodyLargeSemi}; + + width: 100%; + height: 52px; + background-color: ${theme.colors.white}; + color: ${theme.colors.black}; + + cursor: pointer; + `} +`; + +export const CancelButton = styled(ActionButton)` + margin-top: 10px; + border-radius: 16px; +`; + +export const buttonFilter = () => + ({ + flexShrink: 0, + height: '30px', + display: 'flex', + alignItems: 'center', + justifyContent: 'flex-end', + margin: 0, + }) as Interpolation; + +export const buttonBase = css` + display: flex; + height: 48px; + width: 100%; + align-items: center; + gap: 15px; + border-radius: 16px; + padding: 0 16px; + font-weight: 600; + font-size: 17px; + transition: transform 0.2s; + + &:focus { + transform: scale(0.95); + } + &:active { + transform: scale(0.95); + } +`; + +export const Button = styled.button` + ${buttonBase} + background-color: #F7F8F9; + color: #222222; +`; + +export const SecondaryButton = styled.button<{ + variant?: 'default' | 'primary' | 'danger'; +}>` + ${buttonBase} + justify-content: center; + border-radius: 9999px; + font-size: 19px; + background-color: ${props => + props.variant === 'primary' ? '#4DAFFF' : props.variant === 'danger' ? '#FF3F40' : '#F0F2F4'}; + color: ${props => (props.variant === 'default' ? '#222222' : '#FFFFFF')}; +`; + +export const SmoothSecondaryButton = styled(SmoothButton)` + ${buttonBase} + justify-content: center; + border-radius: 9999px; + font-size: 19px; + background-color: #ff3f40; + color: #ffffff; +`; + +export const SmoothGreenButton = styled(SmoothButton)` + ${buttonBase} + justify-content: center; + border-radius: 9999px; + font-size: 19px; + background-color: #29e236; + color: #42b305ac; +`; + +export const HeaderWrapper = styled.header` + margin-top: 21px; +`; + +export const HeaderTitle = styled.h2` + margin-top: 10px; + font-weight: 600; + color: #222226f3; + font-size: 22px; +`; + +export const HeaderDescription = styled.p` + margin-top: 12px; + font-weight: 500; + color: #33333e; + font-size: 17px; + line-height: 24px; + white-space: break-spaces; +`; + +export const List = styled.ul` + margin-top: 24px; + padding-top: 24px; + border-top: 1px solid #f5f5f5; + display: flex; + flex-direction: column; + gap: 16px; +`; + +export const ListItem = styled.li` + display: flex; + align-items: center; + gap: 12px; + font-weight: 600; + color: #999999; + font-size: 15px; +`; + +export const ButtonGroup = styled.div` + margin-top: 20px; + display: flex; + gap: 16px; +`; + +export const DefaultViewHeader = styled.header` + display: flex; + height: 72px; + align-items: center; + padding-left: 8px; + margin-bottom: 16px; +`; + +export const DefaultViewTitle = styled.h2` + font-weight: 600; + color: #222226f3; + font-size: 19px; +`; + +export const ButtonContainer = styled.div` + display: flex; + flex-direction: column; + gap: 12px; +`; + +export const DangerButton = styled.button` + ${buttonBase} + background-color: #fff0f0; + color: #ff3f40; +`; + +export const GreenButton = styled.button` + ${buttonBase} + + background-color: #4ade8015; + color: #42b305ac; +`; diff --git a/services/ahhachul.com/src/components/common/postDropEllipsis/index.ts b/services/ahhachul.com/src/components/common/postDropEllipsis/index.ts new file mode 100644 index 000000000..4d2d1b29c --- /dev/null +++ b/services/ahhachul.com/src/components/common/postDropEllipsis/index.ts @@ -0,0 +1 @@ +export { default as PostDropEllipsis } from './PostDropEllipsis.component'; diff --git a/services/ahhachul.com/src/components/common/readOnlyEditor/ReadOnlyEditor.styled.tsx b/services/ahhachul.com/src/components/common/readOnlyEditor/ReadOnlyEditor.styled.tsx index b60c27682..f81223ad0 100644 --- a/services/ahhachul.com/src/components/common/readOnlyEditor/ReadOnlyEditor.styled.tsx +++ b/services/ahhachul.com/src/components/common/readOnlyEditor/ReadOnlyEditor.styled.tsx @@ -4,4 +4,8 @@ export const EditorContainer = styled.div` width: 100%; padding: 20px; position: relative; + + & > div > div > p { + font-size: 13px; + } `; diff --git a/services/ahhachul.com/src/components/common/subwayLinePicker/SubwayLinePicker.component.tsx b/services/ahhachul.com/src/components/common/subwayLinePicker/SubwayLinePicker.component.tsx index 816544a3b..0e463be04 100644 --- a/services/ahhachul.com/src/components/common/subwayLinePicker/SubwayLinePicker.component.tsx +++ b/services/ahhachul.com/src/components/common/subwayLinePicker/SubwayLinePicker.component.tsx @@ -3,13 +3,30 @@ import React, { useReducer } from 'react'; import { Drawer } from 'vaul'; import { ChevronIcon } from '@/assets/icons/system'; -import { subwayLineOptions } from '@/constants'; import type { SubwayLineType } from '@/types'; import * as S from './SubwayLinePicker.styled'; import { SelectMolecules } from '../form/molecules'; +const subwayLineOptions = { + '1': '1ํ˜ธ์„ ', + '2': '2ํ˜ธ์„ ', + '3': '3ํ˜ธ์„ ', + '4': '4ํ˜ธ์„ ', + '5': '5ํ˜ธ์„ ', + '6': '6ํ˜ธ์„ ', + '7': '7ํ˜ธ์„ ', + '8': '8ํ˜ธ์„ ', + '9': '9ํ˜ธ์„ ', + '11': '๊ฒฝ์˜์ค‘์•™์„ ', + '13': '๊ณตํ•ญ์ฒ ๋„', + '15': '์„œํ•ด์„ ', + '16': '์ˆ˜์ธ๋ถ„๋‹น์„ ', + '18': '์‹ ๋ถ„๋‹น์„ ', + '20': '์šฐ์ด์‹ ์„ค๊ฒฝ์ „์ฒ ', +}; + interface SubwayLinePickerProps { name: string; title: string; @@ -34,16 +51,25 @@ const SubwayLinePicker: React.FC = ({ }; return ( - + - {selectedLine ? subwayLineOptions[selectedLine] : buttonLabel} + + {selectedLine + ? subwayLineOptions[selectedLine as keyof typeof subwayLineOptions] + : buttonLabel} + - - + + ์ทจ์†Œ @@ -59,7 +85,7 @@ const SubwayLinePicker: React.FC = ({ /> - + ); diff --git a/services/ahhachul.com/src/components/common/subwayLinePicker/SubwayLinePicker.styled.ts b/services/ahhachul.com/src/components/common/subwayLinePicker/SubwayLinePicker.styled.ts index a7dc77e4d..9710d586e 100644 --- a/services/ahhachul.com/src/components/common/subwayLinePicker/SubwayLinePicker.styled.ts +++ b/services/ahhachul.com/src/components/common/subwayLinePicker/SubwayLinePicker.styled.ts @@ -21,22 +21,23 @@ export const SelectButton = styled.button<{ isActive: boolean }>` } `; -export const DrawerOverlay = styled(Drawer.Overlay)` +export const overlay = css` position: fixed; top: 0; right: 0; bottom: 0; left: 0; background-color: rgba(0, 0, 0, 0.4); + z-index: 999999990; `; -export const DrawerContainer = styled(Drawer.Content)` +export const drawerContainer = css` z-index: 999999999; display: flex; flex-direction: column; border-top-left-radius: 10px; border-top-right-radius: 10px; - height: max-content; + max-height: 96%; position: fixed; bottom: 0; left: 0; @@ -85,4 +86,8 @@ export const subwayList = css` gap: 8px; justify-items: center; padding: 16px 0; + + & > button { + min-width: 109px; + } `; diff --git a/services/ahhachul.com/src/components/common/toast/Toast.component.tsx b/services/ahhachul.com/src/components/common/toast/Toast.component.tsx index cb0ff5c3b..ca3b0473f 100644 --- a/services/ahhachul.com/src/components/common/toast/Toast.component.tsx +++ b/services/ahhachul.com/src/components/common/toast/Toast.component.tsx @@ -1 +1,143 @@ -export {}; +import type React from 'react'; +import { useEffect } from 'react'; + +import { css } from '@emotion/react'; +import styled from '@emotion/styled'; +import { AlertCircle, CheckCircle, Info, X, AlertTriangle } from 'lucide-react'; + +import { useToastStore, type ToastType } from '@/stores/toast'; + +interface ToastProps { + id: string; + message: string; + type: ToastType; + onClose: () => void; +} + +const toastIcons = { + success: CheckCircle, + warning: AlertTriangle, + info: Info, + error: AlertCircle, +}; + +const getToastStyle = (type: ToastType) => { + switch (type) { + case 'success': + return css` + background-color: #22c55e; + color: white; + `; + case 'warning': + case 'error': + return css` + background-color: #ef4444; + color: white; + `; + case 'info': + return css` + background-color: #3b82f6; + color: white; + `; + default: + return css` + background-color: #ef4444; + color: white; + `; + } +}; + +const ToastContainer = styled.div` + position: fixed; + bottom: 3.3rem; + right: 1rem; + left: 1rem; + width: calc(100% - 2rem); + margin: 0 auto; + z-index: 50; + display: flex; + flex-direction: column; + align-items: flex-end; +`; + +const ToastWrapper = styled.div<{ type: ToastType }>` + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + max-width: 28rem; + padding: 1rem; + margin-bottom: 0.5rem; + border-radius: 0.375rem; + box-shadow: + 0 4px 6px -1px rgba(0, 0, 0, 0.1), + 0 2px 4px -1px rgba(0, 0, 0, 0.06); + ${({ type }) => getToastStyle(type)} +`; + +const ToastContent = styled.div` + display: flex; + align-items: center; +`; + +const ToastMessage = styled.span` + font-weight: 500; + margin-left: 0.5rem; +`; + +const CloseButton = styled.button` + display: inline-flex; + align-items: center; + justify-content: center; + width: 1.5rem; + height: 1.5rem; + border-radius: 0.25rem; + color: white; + &:hover { + background-color: rgba(255, 255, 255, 0.2); + } +`; + +const Toast: React.FC = ({ message, type, onClose }) => { + const Icon = toastIcons[type]; + + useEffect(() => { + const timer = setTimeout(() => { + onClose(); + }, 3000); + + return () => clearTimeout(timer); + }, [onClose]); + + return ( + + + + {message} + + + + + + ); +}; + +const ToastContainerComponent: React.FC = () => { + const { toasts, removeToast } = useToastStore(); + + return ( + + {toasts.map(toast => ( + removeToast(toast.id)} + /> + ))} + + ); +}; + +export default ToastContainerComponent; diff --git a/services/ahhachul.com/src/components/common/toast/Toast.styled.ts b/services/ahhachul.com/src/components/common/toast/Toast.styled.ts deleted file mode 100644 index cb0ff5c3b..000000000 --- a/services/ahhachul.com/src/components/common/toast/Toast.styled.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/services/ahhachul.com/src/components/common/toast/index.ts b/services/ahhachul.com/src/components/common/toast/index.ts new file mode 100644 index 000000000..2369d2cba --- /dev/null +++ b/services/ahhachul.com/src/components/common/toast/index.ts @@ -0,0 +1 @@ +export { default as ToastContainerComponent } from './Toast.component'; diff --git a/services/ahhachul.com/src/components/domain/auth/centerFixedLogo/CenterFixedLogo.component.tsx b/services/ahhachul.com/src/components/domain/auth/centerFixedLogo/CenterFixedLogo.component.tsx index 84e74075a..91c18a968 100644 --- a/services/ahhachul.com/src/components/domain/auth/centerFixedLogo/CenterFixedLogo.component.tsx +++ b/services/ahhachul.com/src/components/domain/auth/centerFixedLogo/CenterFixedLogo.component.tsx @@ -1,15 +1,14 @@ -import { LogoIcon } from '@/assets/icons/system'; -import LogoImg from '@/assets/images/logo.png'; +import { LogoIcon, LogoTextIcon } from '@/assets/icons/system'; import * as S from './CenterFixedLogo.styled'; const CenterFixedLogo = () => { return ( - ahhachul-app-logo +
๋” ํŽธํ•œ ์ง€ํ•˜์ฒ ์„ ๋งŒ๋“œ๋Š” - +
); diff --git a/services/ahhachul.com/src/components/domain/auth/nickname/NicknameSetup.css.ts b/services/ahhachul.com/src/components/domain/auth/nickname/NicknameSetup.css.ts new file mode 100644 index 000000000..45656867f --- /dev/null +++ b/services/ahhachul.com/src/components/domain/auth/nickname/NicknameSetup.css.ts @@ -0,0 +1,85 @@ +import { Interpolation, Theme } from '@emotion/react'; + +import { mixins } from '@/styles'; + +export const form = [mixins.fullWidth, mixins.flexColumn] as Interpolation; + +export const inputGroup = [ + mixins.flexColumn, + () => ({ + position: 'relative', + marginBottom: '32px', + + '& > span': { + color: 'black', + fontSize: '14px', + fontWeight: '600', + marginBottom: '14px', + }, + + '& > input': { + border: '1px solid rgb(196, 212, 252, 0.37)', + height: '44px', + borderRadius: '6px', + padding: '0 12px', + color: 'black', + fontSize: '14px', + caretColor: 'rgba(0, 255, 163, 0.5)', + + '&::placeholder': { + fontSize: '14px', + color: 'black', + }, + + '&[aria-invalid="true"]': { + borderColor: 'red', + }, + }, + + '& > p': { + display: 'inline-flex', + alignItems: 'center', + color: 'red', + fontSize: '14px', + marginTop: '12px', + gap: '6px', + + '& > div > svg > path': { + fill: 'red', + stroke: 'black', + + '&:first-of-type': { + stroke: 'red', + }, + }, + }, + }), +] as Interpolation; + +export const btnWrap = [ + mixins.fullWidth, + () => ({ + position: 'fixed', + bottom: 0, + left: '50%', + transform: 'translateX(-50%)', + background: 'white', + padding: '16px 20px 24px', + + '& > button': { + padding: '0 14px', + fontSize: '14px', + width: '100%', + height: '48px', + background: 'blue', + color: 'white', + fontWeight: '600', + borderRadius: '8px', + + '&:disabled': { + color: 'black', + opacity: 0.75, + }, + }, + }), +] as Interpolation; diff --git a/services/ahhachul.com/src/components/domain/auth/nickname/NicknameSetup.tsx b/services/ahhachul.com/src/components/domain/auth/nickname/NicknameSetup.tsx new file mode 100644 index 000000000..9d6a7af4c --- /dev/null +++ b/services/ahhachul.com/src/components/domain/auth/nickname/NicknameSetup.tsx @@ -0,0 +1,40 @@ +import React, { type HTMLAttributes } from 'react'; + +import { useCheckNickname } from '@/hooks/useCheckNickname'; + +import * as S from './NicknameSetup.css'; + +interface NicknameSetupProps extends HTMLAttributes { + nickname: string; + handleChange: (e: React.ChangeEvent) => void; + handleSubmit: (e: React.FormEvent) => void; +} + +export const NicknameSetup = ({ + nickname, + handleChange, + handleSubmit, + ...props +}: NicknameSetupProps) => { + const { disabled, errorMessage } = useCheckNickname({ nickname }); + + return ( +
+
+ ๋‹‰๋„ค์ž„ ์„ค์ • + + {errorMessage && ( +

+ {/* */} + {errorMessage} +

+ )} +
+
+ +
+
+ ); +}; diff --git a/services/ahhachul.com/src/components/domain/community/edit/template/EditCommunity.component.tsx b/services/ahhachul.com/src/components/domain/community/edit/template/EditCommunity.component.tsx index 9c498bebb..bf0ebeabb 100644 --- a/services/ahhachul.com/src/components/domain/community/edit/template/EditCommunity.component.tsx +++ b/services/ahhachul.com/src/components/domain/community/edit/template/EditCommunity.component.tsx @@ -1,7 +1,7 @@ import { FormProvider } from 'react-hook-form'; import { FormComponent } from '@/components'; -import { lostFoundTypeOptions } from '@/constants'; +import { communityTypeFormOptions } from '@/constants'; import { useEditCommunityForm } from '@/hooks/domain/community'; import { useFetchCommunityDetail } from '@/services/community'; import { useActivity } from '@/stackflow'; @@ -41,7 +41,7 @@ const EditCommunity = ({ id }: WithPostId) => { onDeleteImg={handleImageDelete} onImgChange={handleImageUpload} /> - + diff --git a/services/ahhachul.com/src/components/domain/community/postDetail/commentInput/CommentInput.component.tsx b/services/ahhachul.com/src/components/domain/community/postDetail/commentInput/CommentInput.component.tsx new file mode 100644 index 000000000..6757e7ab2 --- /dev/null +++ b/services/ahhachul.com/src/components/domain/community/postDetail/commentInput/CommentInput.component.tsx @@ -0,0 +1,61 @@ +import { useQueryClient } from '@tanstack/react-query'; + +import { sleep } from '@ahhachul/utils'; + +import { UiComponent } from '@/components'; +import { useAuth } from '@/contexts'; +import { usePostComment } from '@/services/comment'; +import { communityKeys } from '@/services/community'; + +type Props = { + id: number; +}; + +const CommuntiyCommentInput = ({ id }: Props) => { + const { + authService: { isAuthenticated }, + } = useAuth(); + const queryClient = useQueryClient(); + const communityCommentQueryKey = communityKeys.comments(id); + const { mutate } = usePostComment(); + + const submitComment = ({ isPrivate, comment }: { isPrivate: boolean; comment: string }) => { + mutate( + { + postId: id, + content: comment, + upperCommentId: null, + isPrivate: isPrivate, + servicePath: 'community-posts', + }, + { + onSuccess: async res => { + queryClient.invalidateQueries({ + queryKey: communityCommentQueryKey, + }); + + await sleep(250); + + const comment = document.querySelector(`[data-comment-id="${res.result.id}"]`); + if (comment) { + comment.scrollIntoView({ + block: 'start', + behavior: 'smooth', + }); + } + }, + }, + ); + }; + + return ( + + ); +}; + +export default CommuntiyCommentInput; diff --git a/services/ahhachul.com/src/components/domain/community/postDetail/commentList/CommunityCommentList.component.tsx b/services/ahhachul.com/src/components/domain/community/postDetail/commentList/CommunityCommentList.component.tsx index a95e32250..7ed291439 100644 --- a/services/ahhachul.com/src/components/domain/community/postDetail/commentList/CommunityCommentList.component.tsx +++ b/services/ahhachul.com/src/components/domain/community/postDetail/commentList/CommunityCommentList.component.tsx @@ -1,15 +1,16 @@ -import { BookmarkIcon } from '@/assets/icons/system'; +// import { BookmarkIcon } from '@/assets/icons/system'; import { UiComponent } from '@/components'; -import { useFetchCommunityCommentList } from '@/services/community'; +import { communityKeys, useFetchCommunityCommentList } from '@/services/community'; import * as S from './CommunityCommentList.styled'; interface CommunityCommentListProps { id: number; commentCnt: number; + isArticleAuthor: boolean; } -const CommunityCommentList = ({ commentCnt, id }: CommunityCommentListProps) => { +const CommunityCommentList = ({ commentCnt, id, isArticleAuthor }: CommunityCommentListProps) => { return ( @@ -17,23 +18,33 @@ const CommunityCommentList = ({ commentCnt, id }: CommunityCommentListProps) => ๋Œ“๊ธ€ {commentCnt ?? 0} - + {/* */} } suspenseFallback={} > - + ); }; -const CommentListInner = ({ id }: Pick) => { +const CommentListInner = ({ + id, + isArticleAuthor, +}: Pick) => { const { data } = useFetchCommunityCommentList(id); - return ; + return ( + + ); }; export default CommunityCommentList; diff --git a/services/ahhachul.com/src/components/domain/community/postDetail/error/CommunityErrorPage.component.tsx b/services/ahhachul.com/src/components/domain/community/postDetail/error/CommunityErrorPage.component.tsx index f66c02133..f8455025e 100644 --- a/services/ahhachul.com/src/components/domain/community/postDetail/error/CommunityErrorPage.component.tsx +++ b/services/ahhachul.com/src/components/domain/community/postDetail/error/CommunityErrorPage.component.tsx @@ -10,15 +10,15 @@ interface CommunityErrorPageProps { } const CommunityErrorPage = ({ error, reset }: CommunityErrorPageProps) => { - const { replace } = useFlow(); + const { pop } = useFlow(); const isDeletedPost = error.response?.status === 404; return isDeletedPost ? ( ์‚ญ์ œ๋œ ๊ฒŒ์‹œ๊ธ€์ž…๋‹ˆ๋‹ค. - replace('HomePage', {}, { animate: false })}> - ํ™ˆ์œผ๋กœ ์ด๋™ํ•˜๊ธฐ + pop()}> + ๋ชฉ๋ก์œผ๋กœ ์ด๋™ํ•˜๊ธฐ ) : ( diff --git a/services/ahhachul.com/src/components/domain/community/postDetail/error/CommunityErrorPage.styled.tsx b/services/ahhachul.com/src/components/domain/community/postDetail/error/CommunityErrorPage.styled.tsx index 68360ffcc..13d16da0c 100644 --- a/services/ahhachul.com/src/components/domain/community/postDetail/error/CommunityErrorPage.styled.tsx +++ b/services/ahhachul.com/src/components/domain/community/postDetail/error/CommunityErrorPage.styled.tsx @@ -11,14 +11,16 @@ export const Container = styled.div` export const Title = styled.p` text-align: center; - font-size: 20px; - font-weight: 700; + ${({ theme }) => css` + ${theme.fonts.labelMedium}; + `} `; export const Description = styled.p` text-align: center; ${({ theme }) => css` - ${theme.fonts.labelMedium}; + ${theme.fonts.bodyMedium}; + color: ${theme.colors.gray[80]}; `} `; diff --git a/services/ahhachul.com/src/components/domain/community/postDetail/headerActions/CommunityHeaderActions.component.tsx b/services/ahhachul.com/src/components/domain/community/postDetail/headerActions/CommunityHeaderActions.component.tsx index bf2378852..fd037d48c 100644 --- a/services/ahhachul.com/src/components/domain/community/postDetail/headerActions/CommunityHeaderActions.component.tsx +++ b/services/ahhachul.com/src/components/domain/community/postDetail/headerActions/CommunityHeaderActions.component.tsx @@ -1,12 +1,9 @@ -import { useReducer } from 'react'; - import { useActivity } from '@stackflow/react'; -import { MoreVerticalIcon, ShareIcon } from '@/assets/icons/system'; +import { ShareIcon } from '@/assets/icons/system'; import { UiComponent } from '@/components'; import { useNativeBridge } from '@/contexts'; -import { useUser } from '@/hooks/domain'; -import { useFlow } from '@/stackflow'; +import { communityKeys } from '@/services/community'; import { getSharePageURL } from '@/utils/share'; import * as S from './CommunityHeaderActions.styled'; @@ -17,9 +14,7 @@ interface CommunityHeaderActionsProps { } const CommunityHeaderActions = ({ id, createdBy }: CommunityHeaderActionsProps) => { - const { push } = useFlow(); - const { user } = useUser(); - const { isActive } = useActivity(); + const { params, isActive } = useActivity(); const { bridge, isBridgeInitialized } = useNativeBridge(); const handleClickShare = () => { @@ -29,51 +24,23 @@ const CommunityHeaderActions = ({ id, createdBy }: CommunityHeaderActionsProps) bridge.send.share(`${targetUrl}/${id}`); }; - const [isOpen, toggle] = useReducer(open => !open, false); - - const isAuthor = user?.memberId === createdBy; - - const actions = isAuthor - ? [ - { - label: '์ˆ˜์ •ํ•˜๊ธฐ', - onClick: () => push('EditLostFoundPage', { id }), - }, - { - label: '์‚ญ์ œํ•˜๊ธฐ', - onClick: () => console.log('์‚ญ์ œํ•˜๊ธฐ'), - }, - ] - : [ - { - label: '์‹ ๊ณ ํ•˜๊ธฐ', - onClick: () => console.log('์‹ ๊ณ ํ•˜๊ธฐ'), - }, - ]; - if (!isActive) return null; return ( - <> - -
- - - - - - - - -
-
- - -
- -
-
- + +
+ + + + + + +
+
); }; diff --git a/services/ahhachul.com/src/components/domain/community/postDetail/headerActions/CommunityHeaderActions.styled.tsx b/services/ahhachul.com/src/components/domain/community/postDetail/headerActions/CommunityHeaderActions.styled.tsx index a5fce08de..8ca749392 100644 --- a/services/ahhachul.com/src/components/domain/community/postDetail/headerActions/CommunityHeaderActions.styled.tsx +++ b/services/ahhachul.com/src/components/domain/community/postDetail/headerActions/CommunityHeaderActions.styled.tsx @@ -1,7 +1,7 @@ import { css } from '@emotion/react'; import styled from '@emotion/styled'; -export const Container = styled.button` +export const Container = styled.section` ${({ theme }) => css` position: fixed; top: 0; diff --git a/services/ahhachul.com/src/components/domain/community/postDetail/index.ts b/services/ahhachul.com/src/components/domain/community/postDetail/index.ts index 033fe3baa..a36f5edcf 100644 --- a/services/ahhachul.com/src/components/domain/community/postDetail/index.ts +++ b/services/ahhachul.com/src/components/domain/community/postDetail/index.ts @@ -4,3 +4,4 @@ export { default as CommunityDetailSkeleton } from './skeleton/CommunityDetail.s export { default as CommunityCommentList } from './commentList/CommunityCommentList.component'; export { default as CommunityCategoryBadge } from './categoryBadge/CommunityCategoryBadge.component'; export { default as CommunityDetailHeaderActions } from './headerActions/CommunityHeaderActions.component'; +export { default as CommuntiyCommentInput } from './commentInput/CommentInput.component'; diff --git a/services/ahhachul.com/src/components/domain/community/postDetail/template/CommunityDetail.component.tsx b/services/ahhachul.com/src/components/domain/community/postDetail/template/CommunityDetail.component.tsx index 65a2443aa..8ef447dea 100644 --- a/services/ahhachul.com/src/components/domain/community/postDetail/template/CommunityDetail.component.tsx +++ b/services/ahhachul.com/src/components/domain/community/postDetail/template/CommunityDetail.component.tsx @@ -2,6 +2,7 @@ import { formatDateTime } from '@ahhachul/utils'; import { CommunityComponent, UiComponent } from '@/components'; import { subwayIconMap } from '@/constants'; +import { useUser } from '@/hooks/domain'; import { useFetchCommunityDetail } from '@/services/community'; import { isLexicalContent } from '@/utils/lexical'; @@ -14,6 +15,9 @@ interface CommunityDetailProps { const CommunityDetail = ({ id }: CommunityDetailProps) => { const { data: post } = useFetchCommunityDetail(id); + const { user } = useUser(); + const isArticleAuthor = +post.createdBy === user?.memberId; + return ( <> @@ -43,7 +47,13 @@ const CommunityDetail = ({ id }: CommunityDetailProps) => { - + + + ); }; diff --git a/services/ahhachul.com/src/components/domain/community/postDetail/template/CommunityDetail.styled.tsx b/services/ahhachul.com/src/components/domain/community/postDetail/template/CommunityDetail.styled.tsx index 8761537cc..6e2c5cc6c 100644 --- a/services/ahhachul.com/src/components/domain/community/postDetail/template/CommunityDetail.styled.tsx +++ b/services/ahhachul.com/src/components/domain/community/postDetail/template/CommunityDetail.styled.tsx @@ -43,6 +43,7 @@ export const AuthorText = styled.span` `; export const DateText = styled.span` + font-size: 11px; color: ${({ theme }) => theme.colors.gray[70]}; `; @@ -96,3 +97,8 @@ export const LexicalContent = styled.div` } } `; + +export const Padding = styled.div` + width: 100%; + height: 194px; +`; diff --git a/services/ahhachul.com/src/components/domain/community/searchResults/searchedList/SearchedList.component.tsx b/services/ahhachul.com/src/components/domain/community/searchResults/searchedList/SearchedList.component.tsx index 6bda2692b..24ed6f852 100644 --- a/services/ahhachul.com/src/components/domain/community/searchResults/searchedList/SearchedList.component.tsx +++ b/services/ahhachul.com/src/components/domain/community/searchResults/searchedList/SearchedList.component.tsx @@ -11,14 +11,17 @@ interface CommunitySearchedListProps { filters: CommunityFilters; keyword?: string; isScale?: boolean; + className?: string; } const CommunitySearchedList = ({ keyword, - filters: { communityType, subwayLineId }, + filters: { communityType, subwayLineId, hashTag }, isScale, + className, }: CommunitySearchedListProps) => { const { data, hasNextPage, isFetchingNextPage, fetchNextPage } = useFetchCommunityList({ + hashTag, subwayLineId, content: keyword, categoryType: communityType, @@ -39,7 +42,7 @@ const CommunitySearchedList = ({ if (!communityArticles.length) return ; return ( - + {communityArticles.map((post, idx) => ( ` padding-top: 99px; - transform: ${({ isScale }) => (isScale ? 'translateY(-50px)' : 'translateY(0)')} + transform: ${({ isScale }) => (isScale ? 'translateY(-50px)' : 'translateY(0)')}; opacity: 0; animation: fadeIn 0.5s ease-in-out forwards; @@ -27,7 +27,7 @@ export const SectionWrapper = styled.section` `; export const ArticleItem = styled.article<{ delay: number }>` - padding: 24px 20px; + padding: 14px 20px; opacity: 0; animation: fadeIn 0.5s ease-in-out forwards; border-bottom: 1px solid ${({ theme }) => theme.colors.gray[20]}; diff --git a/services/ahhachul.com/src/components/domain/community/searchResults/skeleton/SearchedList.skeleton.tsx b/services/ahhachul.com/src/components/domain/community/searchResults/skeleton/SearchedList.skeleton.tsx index b194ffe97..91949a628 100644 --- a/services/ahhachul.com/src/components/domain/community/searchResults/skeleton/SearchedList.skeleton.tsx +++ b/services/ahhachul.com/src/components/domain/community/searchResults/skeleton/SearchedList.skeleton.tsx @@ -14,8 +14,8 @@ const SearchedListSkeleton = ({ isScale }: SearchedListSkeletonProps) => { - - + + {/* ์ด๋ฏธ์ง€ ์Šค์ผˆ๋ ˆํ†ค */} diff --git a/services/ahhachul.com/src/components/domain/complaint/index.ts b/services/ahhachul.com/src/components/domain/complaint/index.ts new file mode 100644 index 000000000..ea26fb322 --- /dev/null +++ b/services/ahhachul.com/src/components/domain/complaint/index.ts @@ -0,0 +1,3 @@ +export { default as ComplaintPanel } from './panel/ComplaintPanel.component'; +export * from './postDetail'; +export * from './searchResults'; diff --git a/services/ahhachul.com/src/components/domain/complaint/panel/ComplaintPanel.component.tsx b/services/ahhachul.com/src/components/domain/complaint/panel/ComplaintPanel.component.tsx index 882630720..213fbe648 100644 --- a/services/ahhachul.com/src/components/domain/complaint/panel/ComplaintPanel.component.tsx +++ b/services/ahhachul.com/src/components/domain/complaint/panel/ComplaintPanel.component.tsx @@ -8,11 +8,7 @@ import { KeyOf, ValueOf } from '@/types'; import * as S from './ComplaintPanel.styled'; -interface ComplaintPanelProps { - layoutCss: ReturnType; -} - -const ComplaintPanel = ({ layoutCss }: ComplaintPanelProps) => { +const ComplaintPanel = () => { const topCards = objectEntries(complaintsContentList).slice(0, 4); const bottomCards = objectEntries(complaintsContentList).slice(4, 7); @@ -22,39 +18,30 @@ const ComplaintPanel = ({ layoutCss }: ComplaintPanelProps) => { styleCss: ReturnType, ) => (
    - {items.map( - ([ - key, - { - // icon, - label, - desc, - }, - ]) => ( -
  • - - {/* */} + {items.map(([key, { icon, label, desc }]) => ( +
  • + + {label}

    {desc}

    - {/* {icon} */} - {/*
    */} -
    -
  • - ), - )} + {icon} + + + + ))}
); return ( - -
- {/* ์ง€ํ•˜์ฒ  ํ™˜๊ฒฝ */} + + + ์ง€ํ•˜์ฒ  ํ™˜๊ฒฝ {renderComplaintCards(topCards, S.topSection)} -
-
- {/* ๊ธด๊ธ‰๋ฏผ์› ์š”์ฒญ */} + + + ๊ธด๊ธ‰๋ฏผ์› ์š”์ฒญ {renderComplaintCards(bottomCards, S.bottomSection)} -
+
); }; diff --git a/services/ahhachul.com/src/components/domain/complaint/panel/ComplaintPanel.styled.tsx b/services/ahhachul.com/src/components/domain/complaint/panel/ComplaintPanel.styled.tsx index bef7d9e13..c8b311ae5 100644 --- a/services/ahhachul.com/src/components/domain/complaint/panel/ComplaintPanel.styled.tsx +++ b/services/ahhachul.com/src/components/domain/complaint/panel/ComplaintPanel.styled.tsx @@ -4,73 +4,59 @@ import styled from '@emotion/styled'; import mixins from '@/styles/mixins'; export const Panel = styled.div` - ${mixins.fullWidth} - ${mixins.flexColumn} - ${mixins.sideGutter} + ${mixins.animatedLayout(false)}; + ${mixins.fullWidth}; + ${mixins.flexColumn}; gap: 36px; - background-color: ${({ theme }) => theme.colors.white}; + padding-top: 16px; `; -// export const Label = styled.span` -// color: ${({ theme }) => theme.color.text[50]}; -// font-size: ${({ theme }) => theme.typography.fontSize[16]}px; -// font-weight: ${({ theme }) => theme.typography.fontWeight[600]}; -// margin-bottom: 16px; -// `; - -// export const Card = styled.div` -// display: flex; -// flex-direction: column; -// width: 100%; -// height: 100%; -// position: relative; -// padding: 16px; -// border-radius: 8px; -// background-color: ${({ theme }) => theme.color.black[500]}; - -// span { -// color: ${({ theme }) => theme.color.text[50]}; -// font-size: ${({ theme }) => theme.typography.fontSize[16]}px; -// font-weight: ${({ theme }) => theme.typography.fontWeight[600]}; -// margin-bottom: 8px; -// } - -// & > p { -// color: ${({ theme }) => theme.color.blueDarkGray[500]}; -// font-size: ${({ theme }) => theme.typography.fontSize[12]}px; -// } - -// & > div { -// position: absolute; -// right: 16px; -// bottom: 10px; -// } -// `; - -// export const CardStats = styled.article` -// position: absolute; -// right: 16px; -// bottom: 10px; -// text-align: right; - -// span { -// color: ${({ theme }) => theme.color.blueDarkGray[500]}; -// font-size: ${({ theme }) => theme.typography.fontSize[12]}px; -// } - -// p { -// color: ${({ theme }) => theme.color.white[700]}; -// font-size: ${({ theme }) => theme.typography.fontSize[12]}px; -// margin-top: 4px; -// margin-bottom: 6px; -// } - -// div { -// color: ${({ theme }) => theme.color.skyBlue[500]}; -// font-size: ${({ theme }) => theme.typography.fontSize[48]}px; -// font-weight: ${({ theme }) => theme.typography.fontWeight[600]}; -// } -// `; +export const Cell = styled.div` + ${mixins.flexColumn}; + gap: 12px; +`; + +export const Label = styled.span` + ${({ theme }) => css` + color: ${theme.colors.black}; + ${theme.fonts.titleLarge}; + `} +`; + +export const Card = styled.div` + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + position: relative; + padding: 16px; + border-radius: 8px; + background-color: #eafcf1; + + span { + color: ${({ theme }) => theme.colors.black}; + font-size: 16px; + font-weight: 600; + margin-bottom: 8px; + } + + & > p { + color: #67696f; + font-size: 12px; + } + + & > div { + position: absolute; + right: 16px; + bottom: 10px; + } + + & > svg { + position: absolute; + right: 16px; + bottom: 10px; + } +`; const grid = css` display: grid; diff --git a/services/ahhachul.com/src/components/domain/complaint/postDetail/categoryBadge/ComplaintCategoryBadge.component.tsx b/services/ahhachul.com/src/components/domain/complaint/postDetail/categoryBadge/ComplaintCategoryBadge.component.tsx new file mode 100644 index 000000000..f305ccaf6 --- /dev/null +++ b/services/ahhachul.com/src/components/domain/complaint/postDetail/categoryBadge/ComplaintCategoryBadge.component.tsx @@ -0,0 +1,14 @@ +import { complaintTypeOptions } from '@/constants'; +import type { ComplaintType } from '@/types/complaint'; + +import * as S from './ComplaintCategoryBadge.styled'; + +interface ComplaintCategoryBadgeProps { + complaintType: ComplaintType; +} + +const ComplaintCategoryBadge = ({ complaintType }: ComplaintCategoryBadgeProps) => { + return {complaintTypeOptions[complaintType]}; +}; + +export default ComplaintCategoryBadge; diff --git a/services/ahhachul.com/src/components/domain/complaint/postDetail/categoryBadge/ComplaintCategoryBadge.styled.tsx b/services/ahhachul.com/src/components/domain/complaint/postDetail/categoryBadge/ComplaintCategoryBadge.styled.tsx new file mode 100644 index 000000000..010833bce --- /dev/null +++ b/services/ahhachul.com/src/components/domain/complaint/postDetail/categoryBadge/ComplaintCategoryBadge.styled.tsx @@ -0,0 +1,15 @@ +import styled from '@emotion/styled'; + +export const Badge = styled.div` + height: 28px; + font-size: 12px; + line-height: 18px; + color: #ffffff; + padding: 0 10px; + display: flex; + align-items: center; + justify-content: center; + background-color: #407ad6; + border-radius: 100px; + width: max-content; +`; diff --git a/services/ahhachul.com/src/components/domain/complaint/postDetail/commentInput/CommentInput.component.tsx b/services/ahhachul.com/src/components/domain/complaint/postDetail/commentInput/CommentInput.component.tsx new file mode 100644 index 000000000..29209867b --- /dev/null +++ b/services/ahhachul.com/src/components/domain/complaint/postDetail/commentInput/CommentInput.component.tsx @@ -0,0 +1,61 @@ +import { useQueryClient } from '@tanstack/react-query'; + +import { sleep } from '@ahhachul/utils'; + +import { UiComponent } from '@/components'; +import { useAuth } from '@/contexts'; +import { usePostComment } from '@/services/comment'; +import { complaintKeys } from '@/services/complaint'; + +type Props = { + id: number; +}; + +const ComplaintCommentInput = ({ id }: Props) => { + const { + authService: { isAuthenticated }, + } = useAuth(); + const queryClient = useQueryClient(); + const complaintCommentQueryKey = complaintKeys.comments(id); + const { mutate } = usePostComment(); + + const submitComment = ({ isPrivate, comment }: { isPrivate: boolean; comment: string }) => { + mutate( + { + postId: id, + content: comment, + upperCommentId: null, + isPrivate: isPrivate, + servicePath: 'complaint-posts', + }, + { + onSuccess: async res => { + queryClient.invalidateQueries({ + queryKey: complaintCommentQueryKey, + }); + + await sleep(250); + + const comment = document.querySelector(`[data-comment-id="${res.result.id}"]`); + if (comment) { + comment.scrollIntoView({ + block: 'start', + behavior: 'smooth', + }); + } + }, + }, + ); + }; + + return ( + + ); +}; + +export default ComplaintCommentInput; diff --git a/services/ahhachul.com/src/components/domain/complaint/postDetail/commentList/ComplaintCommentList.component.tsx b/services/ahhachul.com/src/components/domain/complaint/postDetail/commentList/ComplaintCommentList.component.tsx new file mode 100644 index 000000000..b827150f4 --- /dev/null +++ b/services/ahhachul.com/src/components/domain/complaint/postDetail/commentList/ComplaintCommentList.component.tsx @@ -0,0 +1,50 @@ +// import { BookmarkIcon } from '@/assets/icons/system'; +import { UiComponent } from '@/components'; +import { complaintKeys, useFetchComplaintCommentList } from '@/services/complaint'; + +import * as S from './ComplaintCommentList.styled'; + +interface ComplaintCommentListProps { + id: number; + commentCnt: number; + isArticleAuthor: boolean; +} + +const ComplaintCommentList = ({ commentCnt, id, isArticleAuthor }: ComplaintCommentListProps) => { + return ( + + + + ๋Œ“๊ธ€ + {commentCnt ?? 0} + + {/* */} + + } + suspenseFallback={} + > + + + + ); +}; + +const CommentListInner = ({ + id, + isArticleAuthor, +}: Pick) => { + const { data } = useFetchComplaintCommentList(id); + + return ( + + ); +}; + +export default ComplaintCommentList; diff --git a/services/ahhachul.com/src/components/domain/complaint/postDetail/commentList/ComplaintCommentList.styled.tsx b/services/ahhachul.com/src/components/domain/complaint/postDetail/commentList/ComplaintCommentList.styled.tsx new file mode 100644 index 000000000..d6c40b4ae --- /dev/null +++ b/services/ahhachul.com/src/components/domain/complaint/postDetail/commentList/ComplaintCommentList.styled.tsx @@ -0,0 +1,23 @@ +import { css } from '@emotion/react'; +import styled from '@emotion/styled'; + +export const Section = styled.section``; + +export const HeaderWrapper = styled.div` + height: 50px; + border-top: 1px solid ${({ theme }) => theme.colors.gray[30]}; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 20px; +`; + +export const CommentCountWrapper = styled.div` + ${({ theme }) => css` + ${theme.fonts.labelMedium}; + color: ${theme.colors.gray[80]}; + display: flex; + align-items: center; + gap: 4px; + `} +`; diff --git a/services/ahhachul.com/src/components/domain/complaint/postDetail/error/ComplaintErrorPage.component.tsx b/services/ahhachul.com/src/components/domain/complaint/postDetail/error/ComplaintErrorPage.component.tsx new file mode 100644 index 000000000..cfaf06a10 --- /dev/null +++ b/services/ahhachul.com/src/components/domain/complaint/postDetail/error/ComplaintErrorPage.component.tsx @@ -0,0 +1,38 @@ +import type { AxiosError } from 'axios'; + +import { useFlow } from '@/stackflow'; + +import * as S from './ComplaintErrorPage.styled'; + +interface ComplaintErrorPageProps { + error: AxiosError; + reset: () => void; +} + +const ComplaintErrorPage = ({ error, reset }: ComplaintErrorPageProps) => { + const { replace } = useFlow(); + + const isDeletedPost = error.response?.status === 404; + + return isDeletedPost ? ( + + ์‚ญ์ œ๋œ ๊ฒŒ์‹œ๊ธ€์ž…๋‹ˆ๋‹ค. + replace('HomePage', {}, { animate: false })}> + ํ™ˆ์œผ๋กœ ์ด๋™ํ•˜๊ธฐ + + + ) : ( + + ์•Œ ์ˆ˜ ์—†๋Š” ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. + + ๊ณ„์† ๋ฐœ์ƒํ•œ๋‹ค๋ฉด ์ž ์‹œ ํ›„ ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.
+ ์ธํ„ฐ๋„ท ์—ฐ๊ฒฐ ์ƒํƒœ๊ฐ€ ์ข‹์ง€ ์•Š์œผ๋ฉด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +
+ + ๋‹ค์‹œ ์‹œ๋„ํ•˜๊ธฐ + +
+ ); +}; + +export default ComplaintErrorPage; diff --git a/services/ahhachul.com/src/components/domain/complaint/postDetail/error/ComplaintErrorPage.styled.tsx b/services/ahhachul.com/src/components/domain/complaint/postDetail/error/ComplaintErrorPage.styled.tsx new file mode 100644 index 000000000..13d16da0c --- /dev/null +++ b/services/ahhachul.com/src/components/domain/complaint/postDetail/error/ComplaintErrorPage.styled.tsx @@ -0,0 +1,44 @@ +import { css } from '@emotion/react'; +import styled from '@emotion/styled'; + +export const Container = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 12px; +`; + +export const Title = styled.p` + text-align: center; + ${({ theme }) => css` + ${theme.fonts.labelMedium}; + `} +`; + +export const Description = styled.p` + text-align: center; + ${({ theme }) => css` + ${theme.fonts.bodyMedium}; + color: ${theme.colors.gray[80]}; + `} +`; + +export const RetryButton = styled.button` + margin-top: 16px; + padding: 8px 16px; + background-color: ${({ theme }) => theme.colors.primary.primary}; + color: ${({ theme }) => theme.colors.gray[90]}; + ${({ theme }) => css` + ${theme.fonts.labelMedium}; + `} + font-weight: 600; + border-radius: 6px; + background-color: white; + border: 1px solid ${({ theme }) => theme.colors.gray[30]}; + transition: background-color 0.2s; + + &:hover { + background-color: ${({ theme }) => `${theme.colors.primary.primary}E6`}; // 90% opacity + } +`; diff --git a/services/ahhachul.com/src/components/domain/complaint/postDetail/headerActions/ComplaintHeaderActions.component.tsx b/services/ahhachul.com/src/components/domain/complaint/postDetail/headerActions/ComplaintHeaderActions.component.tsx new file mode 100644 index 000000000..eef606df6 --- /dev/null +++ b/services/ahhachul.com/src/components/domain/complaint/postDetail/headerActions/ComplaintHeaderActions.component.tsx @@ -0,0 +1,47 @@ +import { useActivity } from '@stackflow/react'; + +import { ShareIcon } from '@/assets/icons/system'; +import { UiComponent } from '@/components'; +import { useNativeBridge } from '@/contexts'; +import { complaintKeys } from '@/services/complaint'; +import { getSharePageURL } from '@/utils/share'; + +import * as S from './ComplaintHeaderActions.styled'; + +interface ComplaintHeaderActionsProps { + id: number; + createdBy: number; +} + +const ComplaintHeaderActions = ({ id, createdBy }: ComplaintHeaderActionsProps) => { + const { params, isActive } = useActivity(); + const { bridge, isBridgeInitialized } = useNativeBridge(); + + const handleClickShare = () => { + if (!isBridgeInitialized) return; + + const targetUrl = getSharePageURL('ComplaintDetailPage'); + bridge.send.share(`${targetUrl}/${id}`); + }; + + if (!isActive) return null; + + return ( + +
+ + + + + + +
+
+ ); +}; + +export default ComplaintHeaderActions; diff --git a/services/ahhachul.com/src/components/domain/complaint/postDetail/headerActions/ComplaintHeaderActions.styled.tsx b/services/ahhachul.com/src/components/domain/complaint/postDetail/headerActions/ComplaintHeaderActions.styled.tsx new file mode 100644 index 000000000..8ca749392 --- /dev/null +++ b/services/ahhachul.com/src/components/domain/complaint/postDetail/headerActions/ComplaintHeaderActions.styled.tsx @@ -0,0 +1,24 @@ +import { css } from '@emotion/react'; +import styled from '@emotion/styled'; + +export const Container = styled.section` + ${({ theme }) => css` + position: fixed; + top: 0; + right: 16px; + background: ${theme.colors.white}; + z-index: ${theme.zIndex.header}; + height: 58px; + display: flex; + align-items: center; + justify-content: center; + gap: 15px; + `} +`; + +export const ActionButton = styled.button` + width: max-content; + display: flex; + align-items: center; + justify-content: center; +`; diff --git a/services/ahhachul.com/src/components/domain/complaint/postDetail/index.ts b/services/ahhachul.com/src/components/domain/complaint/postDetail/index.ts new file mode 100644 index 000000000..91ecaf042 --- /dev/null +++ b/services/ahhachul.com/src/components/domain/complaint/postDetail/index.ts @@ -0,0 +1,8 @@ +export { default as ComplaintDetail } from './template/ComplaintDetail.component'; +export { default as ComplaintCategoryBadge } from './categoryBadge/ComplaintCategoryBadge.component'; +export { default as ComplaintCommentList } from './commentList/ComplaintCommentList.component'; +export { default as ComplaintDetailHeaderActions } from './headerActions/ComplaintHeaderActions.component'; +export { default as ComplaintDetailSkeleton } from './skeleton/ComplaintDetail.skeleton'; +export { default as ComplaintErrorPage } from './error/ComplaintErrorPage.component'; +export { default as SendComplaintMessage } from './sendMessage/SendMessage.component'; +export { default as ComplaintCommentInput } from './commentInput/CommentInput.component'; diff --git a/services/ahhachul.com/src/components/domain/complaint/postDetail/sendMessage/SendMessage.component.tsx b/services/ahhachul.com/src/components/domain/complaint/postDetail/sendMessage/SendMessage.component.tsx new file mode 100644 index 000000000..e7e0ff50d --- /dev/null +++ b/services/ahhachul.com/src/components/domain/complaint/postDetail/sendMessage/SendMessage.component.tsx @@ -0,0 +1,116 @@ +import { UiComponent } from '@/components'; +import { getSubwayComplaintCallNumber, subwayLineOptions } from '@/constants'; +import { useNativeBridge } from '@/contexts'; +import { useUser } from '@/hooks/domain'; +import type { ComplaintType, ShortComplaintType } from '@/types/complaint'; +import { getSharePageURL } from '@/utils/share'; + +const formatComplaintTypeToKoSentence = (complaintType?: ComplaintType) => { + if (!complaintType) return; + + switch (complaintType) { + case 'ENVIRONMENTAL_COMPLAINT': + return 'ํ™˜๊ฒฝ ๋ฏผ์›์ด ๋ฐœ์ƒํ–ˆ์–ด์š”.'; + case 'TEMPERATURE_CONTROL': + return '์˜จ๋„์กฐ์ ˆ ๋ฏผ์›์ด ๋ฐœ์ƒํ–ˆ์–ด์š”.'; + case 'DISORDER': + return '์งˆ์„œ์ €ํ•ด ๋ฏผ์›์ด ๋ฐœ์ƒํ–ˆ์–ด์š”.'; + case 'ANNOUNCEMENT': + return '์•ˆ๋‚ด๋ฐฉ์†ก ๋ฏผ์›์ด ๋ฐœ์ƒํ–ˆ์–ด์š”.'; + case 'EMERGENCY_PATIENT': + return '์‘๊ธ‰ํ™˜์ž ๋ฏผ์›์ด ๋ฐœ์ƒํ–ˆ์–ด์š”.'; + case 'VIOLENCE': + return 'ํญ๋ ฅ ๋ฏผ์›์ด ๋ฐœ์ƒํ–ˆ์–ด์š”.'; + case 'SEXUAL_HARASSMENT': + return '์„ฑ์ถ”ํ–‰ ๋ฏผ์›์ด ๋ฐœ์ƒํ–ˆ์–ด์š”.'; + default: + return '๋ฏผ์›์ด ๋ฐœ์ƒํ–ˆ์–ด์š”.'; + } +}; + +const formatComplaintShortContentToKoSentence = (shortComplaintType?: ShortComplaintType) => { + if (!shortComplaintType) return; + + switch (shortComplaintType) { + case 'WASTE': + return '์˜ค๋ฌผ์ด ์žˆ์–ด์š”!'; + case 'VOMIT': + return 'ํ† ์‚ฌ๋ฌผ์ด ์žˆ์–ด์š”!'; + case 'VENTILATION_REQUEST': + return '์•ˆ์ข‹์€ ๋ƒ„์ƒˆ๊ฐ€ ๋‚˜์š”, ํ™˜๊ธฐ ์ข€ ๋ถ€ํƒ๋“œ๋ ค์š”!'; + case 'NOISY': + return '์•ˆ๋‚ด๋ฐฉ์†ก์ด ๋„ˆ๋ฌด ์ปค์„œ ์‹œ๋„๋Ÿฌ์›Œ์š”!'; + case 'NOT_HEARD': + return '์•ˆ๋‚ด๋ฐฉ์†ก์ด ๋„ˆ๋ฌด ์ž‘์•„์„œ ์•ˆ๋“ค๋ ค์š”!'; + case 'TOO_HOT': + return '๋„ˆ๋ฌด ๋”์›Œ์š”! ์˜จ๋„ ์ข€ ๋‚ฎ์ถฐ์ฃผ์„ธ์š”.'; + case 'TOO_COLD': + return '๋„ˆ๋ฌด ์ถ”์›Œ์š”! ์˜จ๋„ ์ข€ ๋†’์—ฌ์ฃผ์„ธ์š”.'; + case 'MOBILE_VENDOR': + return '์ด๋™์ƒ์ธ์ด ๋ฌผ๊ฑด์„ ํŒ”์•„์š”!'; + case 'DRUNK': + return '์ทจ๊ฐ์ด ๋Œ์•„๋‹ค๋…€์š”!'; + case 'HOMELESS': + return '์ง€ํ•˜์ฒ ์—์„œ ๋…ธ์ˆ™์„ ํ•˜๊ณ  ๊ณ„์„ธ์š”!'; + case 'BEGGING': + return '์ง€ํ•˜์ฒ ์—์„œ ๊ตฌ๊ฑธํ•˜๊ณ  ๊ณ„์‹  ๋ถ„์ด ์žˆ์–ด์š”!'; + case 'RELIGIOUS_ACTIVITY': + return '์ง€ํ•˜์ฒ ์—์„œ ์ข…๊ตํ–‰์œ„ํ•˜๊ณ  ๊ณ„์‹  ๋ถ„์ด ์žˆ์–ด์š”!'; + case 'SELF': + return '๋ณธ์ธ์ด ํ™˜์ž์ž…๋‹ˆ๋‹ค.'; + case 'WITNESS': + return '๋ณธ์ธ์€ ๋ชฉ๊ฒฉ์ž์ž…๋‹ˆ๋‹ค.'; + case 'VICTIM': + return '๋ณธ์ธ์ด ํ”ผํ•ด์ž์ž…๋‹ˆ๋‹ค.'; + default: + return '๋ฏผ์›์ด ๋ฐœ์ƒํ–ˆ์–ด์š”'; + } +}; + +type Props = { + id: number; + complaintType: ComplaintType; + shortContentType: ShortComplaintType; + subwayLineId: number; + createdBy: number; +}; + +export default function SendComplaintMessage({ + id, + complaintType, + shortContentType, + subwayLineId, + createdBy, +}: Props) { + const { user } = useUser(); + const { bridge, isBridgeInitialized } = useNativeBridge(); + + const isAuthor = user?.memberId === createdBy; + const lineName = subwayLineOptions[subwayLineId.toString() as keyof typeof subwayLineOptions]; + const targetUrl = getSharePageURL('ComplaintDetailPage'); + const url = `${targetUrl}/${id}`; + + const messageContent = [ + `${lineName} ${formatComplaintTypeToKoSentence(complaintType)}`, + formatComplaintShortContentToKoSentence(shortContentType), + '', + '์•„ํ•˜์ฒ ์—์„œ ์ž์„ธํ•œ ๋‚ด์šฉ์„ ํ™•์ธํ•ด์ฃผ์„ธ์š”.', + '', + url, + ].join('\n'); + + const handleClick = () => { + if (isBridgeInitialized) { + const phoneNumber = getSubwayComplaintCallNumber(+subwayLineId); + bridge.send.sendTextMessage(phoneNumber, messageContent); + } + }; + + if (!isAuthor) return null; + + return ( + + ์ฆ‰์‹œ ๋ฏผ์› ๋ณด๋‚ด๊ธฐ + + ); +} diff --git a/services/ahhachul.com/src/components/domain/complaint/postDetail/skeleton/ComlaintDetail.skeleton.styled.tsx b/services/ahhachul.com/src/components/domain/complaint/postDetail/skeleton/ComlaintDetail.skeleton.styled.tsx new file mode 100644 index 000000000..767900209 --- /dev/null +++ b/services/ahhachul.com/src/components/domain/complaint/postDetail/skeleton/ComlaintDetail.skeleton.styled.tsx @@ -0,0 +1,105 @@ +import styled from '@emotion/styled'; + +export const ArticleWrapper = styled.article` + padding-top: 20px; + opacity: 0; + animation: fadeIn 0.5s ease-in-out forwards; + @media (prefers-reduced-motion: reduce) { + animation: none; + } + + @keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } + } +`; + +export const ContentWrapper = styled.div` + padding: 0 20px; +`; + +export const TitleWrapper = styled.div` + padding-top: 13px; + padding-bottom: 16px; +`; + +export const MetaWrapper = styled.div` + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + padding-bottom: 16px; + border-bottom: 1px solid ${({ theme }) => theme.colors.gray[20]}; +`; + +export const MetaLeftSection = styled.div` + display: flex; + align-items: center; + gap: 4px; + ${({ theme }) => theme.fonts.bodyMedium}; +`; + +export const MetaRightSection = styled.div` + display: flex; + align-items: center; +`; + +export const ContentSection = styled.div` + padding: 24px 0; + margin-bottom: 12px; +`; + +export const BottomSection = styled.div` + border-top: 1px solid ${({ theme }) => theme.colors.gray[30]}; + height: 50px; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 20px; +`; + +export const BottomLeftSection = styled.div` + display: flex; + align-items: center; + gap: 4px; +`; + +export const BottomRightSection = styled.div` + display: flex; + align-items: center; +`; + +export const CommentsSection = styled.section` + opacity: 0; + animation: fadeIn 0.5s ease-in-out forwards; + animation-delay: 100ms; +`; + +export const CommentItem = styled.div<{ index: number }>` + display: flex; + flex-direction: column; + border-bottom: 1px solid ${({ theme }) => theme.colors.gray[20]}; + background-color: ${({ theme }) => theme.colors.gray[10]}; + padding: 16px 20px; + opacity: 0; + animation: fadeIn 0.5s ease-in-out forwards; + animation-delay: ${({ index }) => (index + 1) * 100}ms; +`; + +export const CommentHeader = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + padding-bottom: 8px; +`; + +export const CommentContent = styled.div` + display: flex; + flex-direction: column; + gap: 12px; + padding-bottom: 20px; +`; diff --git a/services/ahhachul.com/src/components/domain/complaint/postDetail/skeleton/ComplaintDetail.skeleton.tsx b/services/ahhachul.com/src/components/domain/complaint/postDetail/skeleton/ComplaintDetail.skeleton.tsx new file mode 100644 index 000000000..f639e1b0a --- /dev/null +++ b/services/ahhachul.com/src/components/domain/complaint/postDetail/skeleton/ComplaintDetail.skeleton.tsx @@ -0,0 +1,67 @@ +import { BaseSkeleton } from '@/components/common'; + +import * as S from './ComlaintDetail.skeleton.styled'; + +const ComplaintDetailSkeleton = () => { + return ( + <> + + + {/* LostTypeBadge ์Šค์ผˆ๋ ˆํ†ค */} + + + {/* ์ œ๋ชฉ ์Šค์ผˆ๋ ˆํ†ค */} + + + + + {/* ๋ฉ”ํƒ€ ์ •๋ณด ์Šค์ผˆ๋ ˆํ†ค */} + + + + + + + + + + + {/* ๋ณธ๋ฌธ ๋‚ด์šฉ ์Šค์ผˆ๋ ˆํ†ค */} + + + + + + + + {/* ํ•˜๋‹จ ๋Œ“๊ธ€ ์นด์šดํŠธ ๋ฐ ๋ถ๋งˆํฌ ์˜์—ญ ์Šค์ผˆ๋ ˆํ†ค */} + + + + + + + + + + + {/* ๋Œ“๊ธ€ ๋ชฉ๋ก ์Šค์ผˆ๋ ˆํ†ค */} + + {new Array(3).fill('').map((_, idx) => ( + + + + + + + + + + + ))} + + + ); +}; + +export default ComplaintDetailSkeleton; diff --git a/services/ahhachul.com/src/components/domain/complaint/postDetail/template/ComplaintDetail.component.tsx b/services/ahhachul.com/src/components/domain/complaint/postDetail/template/ComplaintDetail.component.tsx new file mode 100644 index 000000000..01d9dcf5b --- /dev/null +++ b/services/ahhachul.com/src/components/domain/complaint/postDetail/template/ComplaintDetail.component.tsx @@ -0,0 +1,68 @@ +import { formatDateTime } from '@ahhachul/utils'; + +import { ComplaintComponent, UiComponent } from '@/components'; +import { subwayIconMap } from '@/constants'; +import { useUser } from '@/hooks/domain'; +import { useFetchComplaintDetail } from '@/services/complaint'; +import { isLexicalContent } from '@/utils/lexical'; + +import * as S from './ComplaintDetail.styled'; + +interface ComplaintDetailProps { + id: number; +} + +const ComplaintDetail = ({ id }: ComplaintDetailProps) => { + const { data: post } = useFetchComplaintDetail(id); + + const { user } = useUser(); + const isArticleAuthor = +post.createdBy === user?.memberId; + + return ( + <> + + + + + + + + {post.title} + + + {post.writer} + {formatDateTime(post.createdAt, { format: 'short' })} + + {subwayIconMap.get(post.subwayLineId)} + + + + + {!isLexicalContent(post.content) ? ( + {post.content} + ) : ( + + + + )} + + + + + + + + ); +}; + +export default ComplaintDetail; diff --git a/services/ahhachul.com/src/components/domain/complaint/postDetail/template/ComplaintDetail.styled.tsx b/services/ahhachul.com/src/components/domain/complaint/postDetail/template/ComplaintDetail.styled.tsx new file mode 100644 index 000000000..6e2c5cc6c --- /dev/null +++ b/services/ahhachul.com/src/components/domain/complaint/postDetail/template/ComplaintDetail.styled.tsx @@ -0,0 +1,104 @@ +import { css } from '@emotion/react'; +import styled from '@emotion/styled'; + +export const ArticleWrapper = styled.article``; + +export const ContentWrapper = styled.div` + padding: 20px 20px 24px; +`; + +export const TitleWrapper = styled.div` + ${({ theme }) => css` + ${theme.fonts.titleLarge}; + color: ${theme.colors.gray[90]}; + padding-top: 13px; + padding-bottom: 16px; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + `} +`; + +export const MetaInfoWrapper = styled.div` + display: flex; + width: 100%; + align-items: center; + justify-content: space-between; + padding-bottom: 16px; + border-bottom: 1px solid ${({ theme }) => theme.colors.gray[20]}; +`; + +export const AuthorDateWrapper = styled.div` + ${({ theme }) => css` + ${theme.fonts.bodyMedium}; + display: flex; + align-items: center; + gap: 4px; + `} +`; + +export const AuthorText = styled.span` + color: ${({ theme }) => theme.colors.gray[80]}; +`; + +export const DateText = styled.span` + font-size: 11px; + color: ${({ theme }) => theme.colors.gray[70]}; +`; + +export const SubwayLineWrapper = styled.div` + ${({ theme }) => css` + ${theme.fonts.labelMedium}; + display: flex; + align-items: center; + color: ${theme.colors.gray[90]}; + font-weight: 400; + `} +`; + +export const Lost112Wrapper = styled.div` + padding: 0 20px; + display: flex; + align-items: center; + height: 56px; + width: 100%; + gap: 8px; +`; + +export const Lost112Text = styled.span` + ${({ theme }) => css` + ${theme.fonts.labelMedium}; + color: ${theme.colors.gray[90]}; + `} +`; + +export const ContentContainer = styled.div` + padding: 0 20px; +`; + +export const TextContent = styled.p` + ${({ theme }) => css` + ${theme.fonts.bodyLargeSemi}; + color: ${theme.colors.gray[90]}; + padding: 0 0 24px; + margin-bottom: 12px; + `} +`; + +export const LexicalContent = styled.div` + padding: 0 0 24px; + + & > div { + padding: 0; + & > div > div { + padding: 0; + border: none; + } + } +`; + +export const Padding = styled.div` + width: 100%; + height: 194px; +`; diff --git a/services/ahhachul.com/src/components/domain/complaint/searchResults/filters/ComplaintFilters.component.tsx b/services/ahhachul.com/src/components/domain/complaint/searchResults/filters/ComplaintFilters.component.tsx new file mode 100644 index 000000000..02c811811 --- /dev/null +++ b/services/ahhachul.com/src/components/domain/complaint/searchResults/filters/ComplaintFilters.component.tsx @@ -0,0 +1,48 @@ +import React from 'react'; + +import { UiComponent } from '@/components'; +import { compalintFilterKeys, subwayLineFilterOptions } from '@/constants'; +import { useActivity } from '@/stackflow'; +import type { IFilterState } from '@/stores/filter'; +import type { ComplaintFilters as TypeComplaintFilters } from '@/types/complaint'; + +import * as S from './ComplaintFilters.styled'; + +interface ComplaintFilterListProps extends Omit, 'loaded'> { + isScale: boolean; + toggleScale: () => void; +} + +const ComplaintFilters: React.FC = ({ + isScale, + toggleScale, + filters, + activatedCount, + handleSelect, + handleReset, +}) => { + const { isActive } = useActivity(); + + return ( + <> + + + + + + + + + + ); +}; + +export default ComplaintFilters; diff --git a/services/ahhachul.com/src/components/domain/complaint/searchResults/filters/ComplaintFilters.styled.tsx b/services/ahhachul.com/src/components/domain/complaint/searchResults/filters/ComplaintFilters.styled.tsx new file mode 100644 index 000000000..fe2c8c197 --- /dev/null +++ b/services/ahhachul.com/src/components/domain/complaint/searchResults/filters/ComplaintFilters.styled.tsx @@ -0,0 +1,48 @@ +import styled from '@emotion/styled'; + +import mixins from '@/styles/mixins'; + +interface MotionProps { + isScale: boolean; +} + +export const Motion = styled.div` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: ${({ isScale }) => (isScale ? '58px' : 0)}; + z-index: ${({ isScale }) => (isScale ? 8 : 0)}; + background-color: ${({ theme, isScale }) => (isScale ? theme.colors.white : 'rgba(0,0,0,0)')}; + transition: background-color 0.15s ease; +`; + +interface FilterGroupProps { + isScale: boolean; + isActive: boolean; +} + +export const FilterGroup = styled.div` + position: fixed; + top: 58px; + left: 0; + flex-direction: column; + width: 100%; + gap: ${({ isScale }) => (isScale ? '9px' : '16px')}; + transform: ${({ isScale }) => (isScale ? 'translateY(-42px)' : 'translateY(0)')}; + transition: all 0.4s ease; + border-bottom: 1px solid ${({ theme }) => theme.colors.gray[20]}; + background-color: ${({ theme }) => theme.colors.white}; + padding-bottom: 16px; + z-index: ${({ isActive, isScale }) => (isActive || isScale ? 9 : 0)}; + display: ${({ isActive }) => (isActive ? 'flex' : 'none')}; +`; + +export const FilterListWrap = styled.div` + ${mixins.fullWidth} + ${mixins.flexAlignCenter} + ${mixins.overflowXScroll} + padding-left: 20px; + padding-right: 20px; + gap: 8px; +`; diff --git a/services/ahhachul.com/src/components/domain/complaint/searchResults/index.ts b/services/ahhachul.com/src/components/domain/complaint/searchResults/index.ts new file mode 100644 index 000000000..775f5a195 --- /dev/null +++ b/services/ahhachul.com/src/components/domain/complaint/searchResults/index.ts @@ -0,0 +1,3 @@ +export { default as FilterList } from './filters/ComplaintFilters.component'; +export { default as SearchedList } from './searchedList/SearchedList.component'; +export { default as SearchedListSkeleton } from './skeleton/SearchedList.skeleton'; diff --git a/services/ahhachul.com/src/components/domain/complaint/searchResults/searchedList/SearchedList.component.tsx b/services/ahhachul.com/src/components/domain/complaint/searchResults/searchedList/SearchedList.component.tsx new file mode 100644 index 000000000..681548b75 --- /dev/null +++ b/services/ahhachul.com/src/components/domain/complaint/searchResults/searchedList/SearchedList.component.tsx @@ -0,0 +1,56 @@ +import { UiComponent } from '@/components'; +import { useIntersectionObserver, useThrottle } from '@/hooks'; +import { useFetchComplaintList } from '@/services/complaint'; +import { StackFlow } from '@/stackflow'; +import { ComplaintFilters } from '@/types/complaint'; +import { extractInfinitePageData } from '@/utils'; + +import * as S from './SearchedList.styled'; + +interface ComplaintSearchedListProps { + filters: ComplaintFilters; + keyword?: string; + isScale?: boolean; +} + +const ComplaintSearchedList = ({ + keyword, + filters: { subwayLineId }, + isScale, +}: ComplaintSearchedListProps) => { + const { data, hasNextPage, isFetchingNextPage, fetchNextPage } = useFetchComplaintList({ + keyword, + subwayLineId, + }); + + const complaintArticles = extractInfinitePageData(data); + + const throttledFetchNextPage = useThrottle(() => { + if (!isFetchingNextPage) { + fetchNextPage(); + } + }, 500); + + const { ref: observer } = useIntersectionObserver({ + callback: throttledFetchNextPage, + }); + + if (!complaintArticles.length) return ; + + return ( + + {complaintArticles.map((post, idx) => ( + + + + ))} + {hasNextPage && ๋” ๋ณด๊ธฐ} + + ); +}; + +export default ComplaintSearchedList; diff --git a/services/ahhachul.com/src/components/domain/complaint/searchResults/searchedList/SearchedList.styled.tsx b/services/ahhachul.com/src/components/domain/complaint/searchResults/searchedList/SearchedList.styled.tsx new file mode 100644 index 000000000..0f50f40b4 --- /dev/null +++ b/services/ahhachul.com/src/components/domain/complaint/searchResults/searchedList/SearchedList.styled.tsx @@ -0,0 +1,16 @@ +import styled from '@emotion/styled'; + +import { mixins } from '@/styles'; + +interface SectionProps { + isScale?: boolean; +} + +export const Section = styled.section` + ${({ isScale }) => mixins.animatedLayout(isScale)}; +`; + +export const ViewMore = styled.span` + opacity: 0; + height: 1px; +`; diff --git a/services/ahhachul.com/src/components/domain/complaint/searchResults/skeleton/SearchedList.skeleton.styled.tsx b/services/ahhachul.com/src/components/domain/complaint/searchResults/skeleton/SearchedList.skeleton.styled.tsx new file mode 100644 index 000000000..e6106fdf6 --- /dev/null +++ b/services/ahhachul.com/src/components/domain/complaint/searchResults/skeleton/SearchedList.skeleton.styled.tsx @@ -0,0 +1,90 @@ +import { css } from '@emotion/react'; +import styled from '@emotion/styled'; + +import { DotIcon } from '@/assets/icons/system'; + +interface SectionWrapperProps { + isScale: boolean; +} +export const SectionWrapper = styled.section` + padding-top: 99px; + transform: ${({ isScale }) => (isScale ? 'translateY(-50px)' : 'translateY(0)')}; + + opacity: 0; + animation: fadeIn 0.5s ease-in-out forwards; + @media (prefers-reduced-motion: reduce) { + animation: none; + } + + @keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } + } +`; + +export const ArticleItem = styled.article<{ delay: number }>` + padding: 20px; + opacity: 0; + animation: fadeIn 0.5s ease-in-out forwards; + border-bottom: 1px solid ${({ theme }) => theme.colors.gray[20]}; +`; + +export const ContentWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 12px; +`; + +export const TopSection = styled.div` + display: flex; + gap: 6px; +`; + +export const TextSection = styled.div` + width: 100%; + display: flex; + flex-direction: column; + gap: 6px; +`; + +export const ImageSection = styled.div` + display: flex; + align-items: center; + justify-content: center; + position: relative; + width: 66px; + min-width: 66px; + aspect-ratio: 1; +`; + +export const BottomSection = styled.div` + display: flex; + align-items: center; + justify-content: space-between; +`; + +export const MetaInfoSection = styled.div` + ${({ theme }) => css` + ${theme.fonts.bodyMedium}; + color: ${theme.colors.gray[80]}; + display: flex; + align-items: center; + gap: 4px; + `} +`; + +export const StyledDotIcon = styled(DotIcon)` + position: relative; + top: 1px; +`; + +export const CountSection = styled.div` + display: flex; + align-items: center; + gap: 2px; + color: ${({ theme }) => theme.colors.gray[80]}; +`; diff --git a/services/ahhachul.com/src/components/domain/complaint/searchResults/skeleton/SearchedList.skeleton.tsx b/services/ahhachul.com/src/components/domain/complaint/searchResults/skeleton/SearchedList.skeleton.tsx new file mode 100644 index 000000000..b194ffe97 --- /dev/null +++ b/services/ahhachul.com/src/components/domain/complaint/searchResults/skeleton/SearchedList.skeleton.tsx @@ -0,0 +1,44 @@ +import { BaseSkeleton } from '@/components/common'; + +import * as S from './SearchedList.skeleton.styled'; + +interface SearchedListSkeletonProps { + isScale: boolean; +} + +const SearchedListSkeleton = ({ isScale }: SearchedListSkeletonProps) => { + return ( + + {new Array(5).fill('').map((_, idx) => ( + + + + + + + + {/* ์ด๋ฏธ์ง€ ์Šค์ผˆ๋ ˆํ†ค */} + + + + + + + + + + + + + + + + + + + ))} + + ); +}; + +export default SearchedListSkeleton; diff --git a/services/ahhachul.com/src/components/domain/home/headerActions/HomeHeaderActions.component.tsx b/services/ahhachul.com/src/components/domain/home/headerActions/HomeHeaderActions.component.tsx new file mode 100644 index 000000000..9ebb7afe7 --- /dev/null +++ b/services/ahhachul.com/src/components/domain/home/headerActions/HomeHeaderActions.component.tsx @@ -0,0 +1,163 @@ +import { useMemo, useReducer, useCallback, useRef } from 'react'; + +import { motion } from 'motion/react'; +import { from, tap, map } from 'rxjs'; + +import { ChevronIcon } from '@/assets/icons/system'; +import { useNativeBridge } from '@/contexts'; +import useOnClickOutside from '@/hooks/useOnClickOutside'; +import { useUserFavoriteStations } from '@/services/user'; +import { useFlow } from '@/stackflow'; +import { useUserStationStore } from '@/stores/subway'; +import type { UserStation } from '@/types'; + +import * as S from './HomeHeaderActions.styled'; + +const wrapperVariants = { + open: { + scaleX: 1, + scaleY: 1, + opacity: 1, + transition: { + type: 'spring', + bounce: 0, + stiffness: 300, + damping: 24, + when: 'beforeChildren', + staggerChildren: 0.02, + duration: 0.15, + }, + }, + closed: { + scaleX: 0.7, + scaleY: 0.7, + opacity: 0, + + transition: { + type: 'spring', + bounce: 0, + duration: 0.4, + staggerChildren: 0.3, + }, + }, +}; + +const iconVariants = { + open: { rotate: 180 }, + closed: { rotate: 0, transition: { duration: 0 } }, +}; + +const Option = ({ + station, + className, + onClick, +}: { + station: UserStation; + className?: string; + onClick: () => void; +}) => { + return ( +
  • + {station.stationName} +
  • + ); +}; + +const HomeHeaderActions = () => { + const { push } = useFlow(); + const { bridge, isBridgeInitialized } = useNativeBridge(); + const [openDialog, toggleDialog] = useReducer(open => !open, false); + + const dialogRef = useRef(null); + useOnClickOutside(dialogRef, () => { + if (!openDialog) return; + toggleDialog(); + }); + + const { mutate: updateUserStations } = useUserFavoriteStations(); + const { userStations } = useUserStationStore(state => state); + const activatedStation = useMemo(() => userStations[0], [userStations]); + + const handleStationClick = useCallback( + (clickedStation: UserStation) => () => { + if (!openDialog) return; + if (activatedStation.stationName === clickedStation.stationName) { + toggleDialog(); + return; + } + + if (isBridgeInitialized) { + bridge.send.haptic(); + } + + from([clickedStation]) + .pipe( + tap(() => toggleDialog()), + map(clicked => [ + clicked, + ...userStations.filter(station => station.stationName !== clicked.stationName), + ]), + tap(updatedStations => { + const req = updatedStations.map(item => ({ + label: item.label, + stationName: item.stationName, + })); + updateUserStations(req); + }), + ) + .subscribe(); + }, + [activatedStation.stationName, openDialog, userStations], + ); + + const handleClickFavoriteStationSetting = () => { + isBridgeInitialized && bridge.send.haptic(); + toggleDialog(); + setTimeout(() => { + push('SettingPage', []); + }, 550); + }; + + return ( +
    + + + + {userStations.map((station, idx) => ( + + +
    + ); +}; + +export default HomeHeaderActions; diff --git a/services/ahhachul.com/src/components/domain/home/headerActions/HomeHeaderActions.styled.tsx b/services/ahhachul.com/src/components/domain/home/headerActions/HomeHeaderActions.styled.tsx new file mode 100644 index 000000000..147771e8c --- /dev/null +++ b/services/ahhachul.com/src/components/domain/home/headerActions/HomeHeaderActions.styled.tsx @@ -0,0 +1,97 @@ +import { css } from '@emotion/react'; +import styled from '@emotion/styled'; + +export const container = css` + display: flex; + align-items: center; + justify-content: center; +`; + +export const button = css` + display: flex; + align-items: center; + gap: 4px; + min-width: 90px; + + border-radius: 6px; + padding: 8px 4px; + + color: #ffffff; + font-size: 18px; + font-weight: bold; + + & > span { + height: 17px; + + & > svg > g > path { + fill: #ffffff; + } + } +`; + +export const menu = css` + display: flex; + flex-direction: column; + gap: 4px; + + width: 192px; + background-color: white; + border-radius: 12px; + padding: 8px; + box-shadow: + 0 10px 15px rgba(0, 0, 0, 0.1), + 0 4px 6px rgba(0, 0, 0, 0.05); + + overflow: hidden; + position: absolute; + top: 26px; + left: -94px; +`; + +export const option = css` + display: flex; + align-items: center; + gap: 8px; + width: 100%; + border-radius: 6px; + padding: 12px 8px; + background-color: white; + color: #272727; + font-size: 16px; + font-weight: 500; + white-space: nowrap; + cursor: pointer; + transition: + background-color 0.2s, + color 0.2s, + transform 0.2s; + + &:first-of-type { + font-weight: 700; + } + + &:hover { + transform: scale(1.02); + background-color: #ebebeb; + } + + &:active { + transform: scale(0.93); + background-color: #ebebeb; + } +`; + +export const Container = styled.nav` + display: grid; + grid-template-columns: repeat(2, 36px); + align-items: center; + gap: 2px; +`; + +export const navigationButtonStyle = css` + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; +`; diff --git a/services/ahhachul.com/src/components/domain/home/headerActions/HomeRightHeaderActions.component.tsx b/services/ahhachul.com/src/components/domain/home/headerActions/HomeRightHeaderActions.component.tsx new file mode 100644 index 000000000..460c63063 --- /dev/null +++ b/services/ahhachul.com/src/components/domain/home/headerActions/HomeRightHeaderActions.component.tsx @@ -0,0 +1,60 @@ +import { css } from '@emotion/react'; + +import { BellIcon, TalkIcon } from '@/assets/icons/system'; +import type { NavigationLink } from '@/components/common/header/HeaderActions.type'; +import { useAuth } from '@/contexts'; +import { StackFlow } from '@/stackflow'; + +import * as S from './HomeHeaderActions.styled'; + +export const NAVIGATION_LINKS: NavigationLink[] = [ + { + icon: ( + path { + stroke: #ffffff; + } + `} + /> + ), + authenticatedRoute: 'TalkPage', + unauthenticatedRoute: 'SignInPage', + }, + { + icon: ( + path { + stroke: #ffffff; + } + `} + /> + ), + authenticatedRoute: 'NotificationPage', + unauthenticatedRoute: 'SignInPage', + }, +] as const; + +const HomeHeaderRightActions = () => { + const { + authService: { isAuthenticated }, + } = useAuth(); + + return ( + + {NAVIGATION_LINKS.map(({ authenticatedRoute, unauthenticatedRoute, icon }) => ( + + {icon} + + ))} + + ); +}; + +export default HomeHeaderRightActions; diff --git a/services/ahhachul.com/src/components/domain/home/index.ts b/services/ahhachul.com/src/components/domain/home/index.ts index 56e72b21f..458b1d847 100644 --- a/services/ahhachul.com/src/components/domain/home/index.ts +++ b/services/ahhachul.com/src/components/domain/home/index.ts @@ -1 +1,4 @@ export * from './panel'; +export * from './stations'; +export { default as HomeHeaderActions } from './headerActions/HomeHeaderActions.component'; +export { default as HomeHeaderRightActions } from './headerActions/HomeRightHeaderActions.component'; diff --git a/services/ahhachul.com/src/components/domain/home/panel/index.ts b/services/ahhachul.com/src/components/domain/home/panel/index.ts index 684fbc6f0..0f3d344bb 100644 --- a/services/ahhachul.com/src/components/domain/home/panel/index.ts +++ b/services/ahhachul.com/src/components/domain/home/panel/index.ts @@ -1,2 +1,3 @@ -export { default as Stations } from './testing/Stations.component'; +export { default as SubwayNews } from './subwayNews/SubwayNews.component'; +export { default as RankHashtag } from './rankHashtag/RankHashtag.component'; export { default as WelcomeMessage } from './welcomeMessage/WelcomeMessage.component'; diff --git a/services/ahhachul.com/src/components/domain/home/panel/rankHashtag/RankHashtag.component.tsx b/services/ahhachul.com/src/components/domain/home/panel/rankHashtag/RankHashtag.component.tsx new file mode 100644 index 000000000..bbf050f87 --- /dev/null +++ b/services/ahhachul.com/src/components/domain/home/panel/rankHashtag/RankHashtag.component.tsx @@ -0,0 +1,85 @@ +import { NoticeGraphic } from '@/assets/graphics'; +import { useAuth } from '@/contexts'; +import { useFetchUserProfile } from '@/services/user'; +import { StackFlow } from '@/stackflow'; + +import * as S from './RankHashtag.styled'; + +interface Hashtag { + id: number; + title: string; + upRank?: number; + downRank?: number; +} + +const MOCK_RANKING: Hashtag[] = [ + { + id: 0, + title: '1ํ˜ธ์„  ๋นŒ๋Ÿฐ', + upRank: 4, + downRank: 3, + }, + { + id: 1, + title: '2ํ˜ธ์„  ์—ฐ์ฐฉ', + upRank: 2, + downRank: 3, + }, + { + id: 2, + title: '์ง ํ…Œํฌ', + upRank: 5, + downRank: 3, + }, + { + id: 3, + title: '๋ฐ์ผ๋ฆฌ๋‰ด์Šค', + upRank: 9, + downRank: 3, + }, + { + id: 4, + title: '๋‹ค์ด์†Œ', + upRank: 18, + downRank: 3, + }, +]; + +const RankHashtag = () => { + const { isCheckingAuthState } = useAuth(); + const { isLoading } = useFetchUserProfile(); + + if (isLoading || isCheckingAuthState) return null; + + return ( + + ์ธ๊ธฐ ํ•ด์‹œํƒœ๊ทธ + + + #1ํ˜ธ์„  ๋นŒ๋Ÿฐ + ์˜ค๋Š˜์˜ ์ธ๊ธฐ ํƒœ๊ทธ 1์œ„! + + + + {MOCK_RANKING.map((item, idx) => ( + + +
    + {idx + 1} + {item.title} +
    + {'-'} +
    +
    + ))} +
    +
    +
    + ); +}; + +export default RankHashtag; diff --git a/services/ahhachul.com/src/components/domain/home/panel/rankHashtag/RankHashtag.styled.ts b/services/ahhachul.com/src/components/domain/home/panel/rankHashtag/RankHashtag.styled.ts new file mode 100644 index 000000000..2aa400968 --- /dev/null +++ b/services/ahhachul.com/src/components/domain/home/panel/rankHashtag/RankHashtag.styled.ts @@ -0,0 +1,171 @@ +import { LazyLoadImage } from 'react-lazy-load-image-component'; +import 'react-lazy-load-image-component/src/effects/opacity.css'; + +import { css } from '@emotion/react'; +import styled from '@emotion/styled'; + +import { DotIcon } from '@/assets/icons/system'; +import { mixins } from '@/styles'; + +export const Container = styled.div` + ${({ theme }) => css` + ${mixins.pagePaddingBottom}; + background-color: ${theme.colors.gray[20]}; + + b { + ${mixins.sideGutter}; + ${theme.fonts.titleSmall}; + color: ${theme.colors.gray[100]}; + } + `} +`; + +export const HashtagList = styled.ul` + ${({ theme }) => css` + margin: 16px 18px 24px; + border-radius: 12px; + background-color: ${theme.colors.gray[10]}; + overflow: hidden; + `} +`; + +export const TopRankBox = styled.article` + ${({ theme }) => css` + height: 76px; + padding: 16px 22px; + border-top-left-radius: 12px; + border-top-right-radius: 12px; + background-color: ${theme.colors.gray[90]}; + display: flex; + flex-direction: column; + position: relative; + + & > b { + padding: 0; + ${theme.fonts.titleMedium}; + color: ${theme.colors.white}; + } + + & > span { + ${theme.fonts.bodyMedium}; + color: ${theme.colors.white}; + } + + & > svg { + position: absolute; + bottom: 0; + right: 0; + } + `} +`; + +export const RankList = styled.div` + width: 100%; + display: flex; + flex-direction: column; + padding: 8px 16px; +`; + +export const RankContent = styled.div` + ${({ theme }) => css` + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px 8px; + + &:not(:last-of-type) { + border-bottom: 1px solid ${theme.colors.gray[20]}; + } + + & > div { + display: flex; + align-items: center; + + & > span { + ${theme.fonts.bodyLarge}; + color: ${theme.colors['key-color']}; + min-width: 18px; + } + + & > b { + padding: 0; + ${theme.fonts.bodyLarge}; + ${theme.colors.gray[90]}; + } + } + + & > span { + ${theme.fonts.bodySmall}; + color: ${theme.colors.gray[90]}; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + } + `} +`; + +export const ImageContainer = styled.div` + display: flex; + align-items: center; + justify-content: center; + position: relative; + width: 66px; + min-width: 66px; + max-width: 66px; + height: 66px; + min-height: 66px; + max-height: 66px; +`; + +export const PostImage = styled(LazyLoadImage)` + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; + border-radius: 8px; +`; + +export const MetaContainer = styled.div` + display: flex; + align-items: center; + justify-content: space-between; +`; + +export const MetaInfo = styled.div` + ${({ theme }) => css` + ${theme.fonts.bodyMedium}; + display: flex; + align-items: center; + gap: 4px; + color: ${theme.colors.gray[80]}; + `} +`; + +export const StyledDotIcon = styled(DotIcon)` + position: relative; + top: 1px; +`; + +export const CommentContainer = styled.div` + ${({ theme }) => css` + ${theme.fonts.bodyMedium}; + display: flex; + align-items: center; + gap: 2px; + color: ${theme.colors.gray[80]}; + `} +`; + +export const lexicalContentStyle = css` + padding: 0; + + & > div > div { + padding: 0; + border: none; + max-height: 46px; + overflow: hidden; + } +`; diff --git a/services/ahhachul.com/src/components/domain/home/panel/subwayNews/SubwayNews.component.tsx b/services/ahhachul.com/src/components/domain/home/panel/subwayNews/SubwayNews.component.tsx new file mode 100644 index 000000000..8ef1908c3 --- /dev/null +++ b/services/ahhachul.com/src/components/domain/home/panel/subwayNews/SubwayNews.component.tsx @@ -0,0 +1,111 @@ +import { subwayIconMap } from '@/constants'; +import { useAuth } from '@/contexts'; +import { useFetchUserProfile } from '@/services/user'; +import { StackFlow } from '@/stackflow'; + +import * as S from './SubwayNews.styled'; + +interface SubwayNews { + id: number; + title: string; + lineNumber: number; + timeElapsed: string; + thumbnailUrl?: string; +} + +const MOCK_NEWS: SubwayNews[] = [ + { + id: 1, + title: 'ํ•œ๋ฐค ๋‚ด๋ณต์ฐจ๋ฆผ ์ง€ํ•˜์ฒ ์—ญ ํ—ค๋งค๋˜ 90๋Œ€โ€ฆโ€˜์ด๊ฒƒโ€™ ๋•Œ๋ฌธ์— ๋ฌด์‚ฌ ๊ท€๊ฐ€ํ•œ๋ฐค ๋‚ด๋ณต์ฐจ๋ฆผ', + lineNumber: 2, + timeElapsed: '2๋ถ„ ์ „', + thumbnailUrl: + 'https://ahhachul-api-dev-bucket.s3.ap-northeast-2.amazonaws.com/230fd7b7-0614-4b9a-95af-d9bc01640ddd', + }, + { + id: 2, + title: 'ๅฐน์„ ๊ณ ์ผ ๋นŒ๋”ฉ ์˜ฅ์ƒ ์ถœ์ž…ํ†ต์ œโ€ฆ์ง€ํ•˜์ฒ  ๋ฌผํ’ˆ๋ณด๊ด€ํ•จ ํ์‡„ ๊ฒ€ํ† ', + lineNumber: 5, + timeElapsed: '2๋ถ„ ์ „', + }, + { + id: 3, + title: '"์ง€ํ•˜์ฒ ์—์„œ \'๋ชฐ๋ž˜ ์ดฌ์˜\' ์ฒ˜๋ฒŒ...์—ฌ์ž ๋งŒ๋‚  ๋•Œ ๊ณผ๊ฑฐ ์•Œ๋ ค์•ผ ํ•˜๋‹ˆ"', + lineNumber: 9, + timeElapsed: '2๋ถ„ ์ „', + thumbnailUrl: + 'https://ahhachul-api-dev-bucket.s3.ap-northeast-2.amazonaws.com/230fd7b7-0614-4b9a-95af-d9bc01640ddd', + }, + { + id: 4, + title: '๊ตฌ๋ฆฌ๋„์‹œ๊ณต์‚ฌ, ์ง€ํ•˜์ฒ  8ํ˜ธ์„  ๊ตฌ๋ฆฌ๊ตฌ๊ฐ„ ์—ญ์‚ฌ ์•ˆ์ „์ ๊ฒ€ ์‹ค์‹œ', + lineNumber: 18, + timeElapsed: '2๋ถ„ ์ „', + thumbnailUrl: + 'https://ahhachul-api-dev-bucket.s3.ap-northeast-2.amazonaws.com/230fd7b7-0614-4b9a-95af-d9bc01640ddd', + }, + { + id: 5, + title: '4ํ˜ธ์„  ์ง€ํ•˜์ฒ  ์—ด์ฐจ ์•ˆ 10๋Œ€ ํ‰๊ธฐ ์žํ•ด...์‘๊ตฌ์กฐ๋ถ€์„œ 119 ๊ธด๊ธ‰', + lineNumber: 4, + timeElapsed: '2๋ถ„ ์ „', + thumbnailUrl: + 'https://ahhachul-api-dev-bucket.s3.ap-northeast-2.amazonaws.com/230fd7b7-0614-4b9a-95af-d9bc01640ddd', + }, +]; + +const SubwayNews = () => { + const { isCheckingAuthState } = useAuth(); + const { isLoading } = useFetchUserProfile(); + + if (isLoading || isCheckingAuthState) return null; + + return ( + + ์‹ค์‹œ๊ฐ„ ์ง€ํ•˜์ฒ  ๋‰ด์Šค + + {MOCK_NEWS.map(news => ( + + + + + + {news.title} + + + + {news.lineNumber && ( + <> + {subwayIconMap.get(news.lineNumber)} + + + )} + {news.timeElapsed} + + + + + {news?.thumbnailUrl && ( + + )} + + + + + ))} + + + ); +}; + +export default SubwayNews; diff --git a/services/ahhachul.com/src/components/domain/home/panel/subwayNews/SubwayNews.styled.ts b/services/ahhachul.com/src/components/domain/home/panel/subwayNews/SubwayNews.styled.ts new file mode 100644 index 000000000..5689d5929 --- /dev/null +++ b/services/ahhachul.com/src/components/domain/home/panel/subwayNews/SubwayNews.styled.ts @@ -0,0 +1,146 @@ +import { LazyLoadImage } from 'react-lazy-load-image-component'; +import 'react-lazy-load-image-component/src/effects/opacity.css'; + +import { css } from '@emotion/react'; +import styled from '@emotion/styled'; + +import { DotIcon } from '@/assets/icons/system'; +import { mixins } from '@/styles'; + +export const SubwayNews = styled.div` + ${({ theme }) => css` + ${mixins.sideGutter}; + padding-top: 24px; + padding-bottom: 32px; + background-color: ${theme.colors.gray[20]}; + + b { + ${theme.fonts.titleSmall}; + color: ${theme.colors.gray[100]}; + } + `} +`; + +export const NewsList = styled.ul` + ${({ theme }) => css` + padding: 8px 18px; + border-radius: 12px; + background-color: ${theme.colors.gray[10]}; + margin-top: 16px; + `} +`; + +export const Article = styled.article` + ${({ theme }) => css` + padding: 16px 0; + + &:not(:last-of-type) { + border-bottom: 1px solid ${theme.colors.gray[20]}; + } + `} +`; + +export const Container = styled.div` + display: flex; + gap: 36px; +`; + +export const ContentWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 6px; +`; + +export const TextContainer = styled.div` + width: 100%; + display: flex; + flex-direction: column; + gap: 6px; +`; + +export const Title = styled.div` + ${({ theme }) => css` + ${theme.fonts.labelMedium}; + color: ${theme.colors.gray[90]}; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + `} +`; + +export const Content = styled.div` + ${({ theme }) => css` + ${theme.fonts.bodyMedium}; + color: ${theme.colors.gray[90]}; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + `} +`; + +export const ImageContainer = styled.div` + display: flex; + align-items: center; + justify-content: center; + position: relative; + width: 66px; + min-width: 66px; + max-width: 66px; + height: 66px; + min-height: 66px; + max-height: 66px; +`; + +export const PostImage = styled(LazyLoadImage)` + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; + border-radius: 8px; +`; + +export const MetaContainer = styled.div` + display: flex; + align-items: center; + justify-content: space-between; +`; + +export const MetaInfo = styled.div` + ${({ theme }) => css` + ${theme.fonts.bodyMedium}; + display: flex; + align-items: center; + gap: 4px; + color: ${theme.colors.gray[80]}; + `} +`; + +export const StyledDotIcon = styled(DotIcon)` + position: relative; + top: 1px; +`; + +export const CommentContainer = styled.div` + ${({ theme }) => css` + ${theme.fonts.bodyMedium}; + display: flex; + align-items: center; + gap: 2px; + color: ${theme.colors.gray[80]}; + `} +`; + +export const lexicalContentStyle = css` + padding: 0; + + & > div > div { + padding: 0; + border: none; + max-height: 46px; + overflow: hidden; + } +`; diff --git a/services/ahhachul.com/src/components/domain/home/panel/testing/Stations.component.tsx b/services/ahhachul.com/src/components/domain/home/panel/testing/Stations.component.tsx deleted file mode 100644 index 5f70231c4..000000000 --- a/services/ahhachul.com/src/components/domain/home/panel/testing/Stations.component.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { useAuth } from '@/contexts'; -import { useFetchUserFavoriteStations } from '@/services/user'; - -const Stations = () => { - const { isCheckingAuthState } = useAuth(); - const { isLoading } = useFetchUserFavoriteStations(); - - if (isLoading || isCheckingAuthState) return null; - - return
    Stations
    ; -}; - -export default Stations; diff --git a/services/ahhachul.com/src/components/domain/home/panel/welcomeMessage/WelcomeMessage.component.tsx b/services/ahhachul.com/src/components/domain/home/panel/welcomeMessage/WelcomeMessage.component.tsx index 751f9333d..33879bc09 100644 --- a/services/ahhachul.com/src/components/domain/home/panel/welcomeMessage/WelcomeMessage.component.tsx +++ b/services/ahhachul.com/src/components/domain/home/panel/welcomeMessage/WelcomeMessage.component.tsx @@ -1,30 +1,23 @@ -import { useState, useEffect } from 'react'; - import { useAuth } from '@/contexts'; import { useFetchUserProfile } from '@/services/user'; import * as S from './WelcomeMessage.styled'; import { getRandomGreeting } from './WelcomeMessage.util'; +const greetingPhrase = getRandomGreeting(); + const WelcomeMessage = () => { const { isCheckingAuthState } = useAuth(); const { data: userInfo, isLoading } = useFetchUserProfile(); - const [greeting, setGreeting] = useState(''); - const displayName = userInfo?.result?.nickname || '์•„ํ•˜์ฒ '; - useEffect(() => { - // ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งˆ์šดํŠธ๋  ๋•Œ ํ•œ ๋ฒˆ๋งŒ ๋žœ๋ค ๋ฌธ๊ตฌ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. - setGreeting(getRandomGreeting()); - }, []); - - if (!greeting || isLoading || isCheckingAuthState) return null; + if (isLoading || isCheckingAuthState) return null; return ( {displayName}๋‹˜, - {greeting} + {greetingPhrase} ); }; diff --git a/services/ahhachul.com/src/components/domain/home/panel/welcomeMessage/WelcomeMessage.styled.tsx b/services/ahhachul.com/src/components/domain/home/panel/welcomeMessage/WelcomeMessage.styled.tsx index b74657deb..c91b62700 100644 --- a/services/ahhachul.com/src/components/domain/home/panel/welcomeMessage/WelcomeMessage.styled.tsx +++ b/services/ahhachul.com/src/components/domain/home/panel/welcomeMessage/WelcomeMessage.styled.tsx @@ -8,7 +8,8 @@ export const WelcomeMessageHeading = styled.h1` ${mixins.sideGutter}; ${theme.fonts.headlineSmall}; - color: ${theme.colors.black}; + color: ${theme.colors.white}; + background-color: ${theme.colors.gray[100]}; b { font-weight: 700; diff --git a/services/ahhachul.com/src/components/domain/home/stations/Stations.component.tsx b/services/ahhachul.com/src/components/domain/home/stations/Stations.component.tsx new file mode 100644 index 000000000..441c55b8c --- /dev/null +++ b/services/ahhachul.com/src/components/domain/home/stations/Stations.component.tsx @@ -0,0 +1,35 @@ +import { useAuth } from '@/contexts'; +import { useUserStationStore } from '@/stores/subway'; + +import * as S from './Stations.styled'; +import SubwayLineFilter from './subwayLineFilter/SubwayLineFilter.component'; +import TrainRealTimes from './trainRealTimes/TrainRealTimes.component'; + +const Stations = () => { + const { isCheckingAuthState } = useAuth(); + const { userStations, setUserStations } = useUserStationStore(state => state); + + const currentStation = userStations[0]; + const currentSubwayLineInfo = currentStation.subwayLineInfoList[0]; + + const realTimesProps = () => ({ + ...currentSubwayLineInfo, + stationId: currentStation.stationId, + stationName: currentStation.stationName, + }); + + if (isCheckingAuthState) return null; + + return ( +
    + + +
    + ); +}; + +export default Stations; diff --git a/services/ahhachul.com/src/components/domain/home/stations/Stations.styled.tsx b/services/ahhachul.com/src/components/domain/home/stations/Stations.styled.tsx new file mode 100644 index 000000000..c4d0d0d8e --- /dev/null +++ b/services/ahhachul.com/src/components/domain/home/stations/Stations.styled.tsx @@ -0,0 +1,12 @@ +import { Interpolation, Theme } from '@emotion/react'; + +import { mixins } from '@/styles'; + +export const section = [ + mixins.fullWidth, + mixins.flexColumn, + { + marginTop: '16px', + marginBottom: '30px', + }, +] as Interpolation; diff --git a/services/ahhachul.com/src/components/domain/home/stations/index.ts b/services/ahhachul.com/src/components/domain/home/stations/index.ts new file mode 100644 index 000000000..735f9e9ab --- /dev/null +++ b/services/ahhachul.com/src/components/domain/home/stations/index.ts @@ -0,0 +1,2 @@ +export { default as Stations } from './Stations.component'; +export { default as SubwayLineFilter } from './subwayLineFilter/SubwayLineFilter.component'; diff --git a/services/ahhachul.com/src/components/domain/home/stations/subwayLineFilter/SubwayLineFilter.component.tsx b/services/ahhachul.com/src/components/domain/home/stations/subwayLineFilter/SubwayLineFilter.component.tsx new file mode 100644 index 000000000..bcfe783ad --- /dev/null +++ b/services/ahhachul.com/src/components/domain/home/stations/subwayLineFilter/SubwayLineFilter.component.tsx @@ -0,0 +1,75 @@ +import React from 'react'; + +import { subwayLineOptions } from '@/constants'; +import { StackFlow } from '@/stackflow'; +import type { IUserStationStore } from '@/stores/subway'; +import type { SubwayLineType, UserStation } from '@/types'; + +import * as S from './SubwayLineFilter.styled'; + +interface SubwayLineFilterProps extends IUserStationStore { + currentStation: UserStation; +} + +const SubwayLineFilter = ({ + userStations, + currentStation, + setUserStations, +}: SubwayLineFilterProps) => { + const reorderStationInfos = (subwayLineId: SubwayLineType) => () => { + const clickedInfo = currentStation.subwayLineInfoList.find( + info => info.subwayLineId === subwayLineId, + ); + const remainingInfos = currentStation.subwayLineInfoList.filter( + info => info.subwayLineId !== subwayLineId, + ); + + const reorderedStationInfos = [clickedInfo, ...remainingInfos]; + + const updatedStation = { + ...currentStation, + subwayLineInfoList: reorderedStationInfos, + }; + + const updatedStations = userStations.map(s => + s.stationName === currentStation.stationName ? updatedStation : s, + ) as UserStation[]; + + setUserStations(updatedStations); + }; + + return ( +
    +
      + {currentStation.subwayLineInfoList.map(info => ( + +
    • + +
    • +
      + ))} +
    + + ์ „์ฒด ๋…ธ์„ ๋„ ๋ณด๊ธฐ + + + + +
    + ); +}; + +export default SubwayLineFilter; diff --git a/services/ahhachul.com/src/components/domain/home/stations/subwayLineFilter/SubwayLineFilter.styled.ts b/services/ahhachul.com/src/components/domain/home/stations/subwayLineFilter/SubwayLineFilter.styled.ts new file mode 100644 index 000000000..ccb2dee02 --- /dev/null +++ b/services/ahhachul.com/src/components/domain/home/stations/subwayLineFilter/SubwayLineFilter.styled.ts @@ -0,0 +1,81 @@ +import { Interpolation, Theme } from '@emotion/react'; + +import { subwayLineHexColors } from '@/constants/subway'; +import { mixins } from '@/styles'; +import { SubwayLineType } from '@/types'; + +export const container = [ + mixins.fullWidth, + mixins.flexJustifySpaceBetween, + ({ colors: { gray } }: Theme) => ({ + backgroundColor: gray[100], + marginBottom: '16px', + }), +]; + +export const filters = [ + mixins.flexAlignCenter, + { flex: 1, paddingLeft: '20px' }, +] as Interpolation; + +export const inherit = (line: SubwayLineType) => ({ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + width: Number(line) < 10 ? '28px' : 'max-content', + height: '28px', + borderRadius: 999999, + padding: '0 8px', + marginRight: '12px', + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + backgroundColor: subwayLineHexColors(Number(line)), + transition: 'transform 150ms ease-out', + + '&:hover': { + transform: 'scale(1.05)', + }, + + '&:active': { + transform: 'scale(0.9)', + }, +}); + +export const filterBtn = (length: number) => ({ + color: 'white', + fontSize: length >= 4 ? '12px' : '14px', + fontWeight: 600, + letterSpacing: '-0.2px', + lineHeight: '0', +}); + +export const link = [ + mixins.flexAlignCenter, + mixins.flexJustifySpaceBetween, + { + fontSize: '14px', + fontWeight: 500, + color: '#e6e6e6', + padding: '12px 20px', + borderRadius: '8px', + transition: 'all 100ms ease-out', + minHeight: '44px', + cursor: 'pointer', + + '&:hover': { + transform: 'scale(1.02)', + // backgroundColor: 'rgba(255, 255, 255, 0.15)', + }, + + '&:active': { + transform: 'scale(0.93)', + // backgroundColor: 'rgba(255, 255, 255, 0.05)', + }, + + '& > svg': { + width: '20px', + height: '20px', + }, + }, +]; diff --git a/services/ahhachul.com/src/components/domain/home/stations/trainAnimation/TrainAnimation.component.tsx b/services/ahhachul.com/src/components/domain/home/stations/trainAnimation/TrainAnimation.component.tsx new file mode 100644 index 000000000..e4ad05bf3 --- /dev/null +++ b/services/ahhachul.com/src/components/domain/home/stations/trainAnimation/TrainAnimation.component.tsx @@ -0,0 +1,47 @@ +import { useLayoutEffect, useRef, useState } from 'react'; + +import { useThrottle } from '@/hooks'; + +import * as S from './TrainAnimation.styled'; +import { TrainEachSection } from './TrainEachSection.component'; +import { TrainIcon } from './TrainIcon.component'; + +const TrainAnimation = () => { + const container = useRef(null); + + const [width, setWidth] = useState(0); + + const detectionViewport = () => { + const width = (container.current as HTMLElement)?.getBoundingClientRect().width; + setWidth(width); + }; + + const handleDetectViewport = useThrottle(detectionViewport, 1000); + + useLayoutEffect(() => { + detectionViewport(); + + window.addEventListener('resize', handleDetectViewport); + + return () => { + window.removeEventListener('resize', handleDetectViewport); + }; + }, []); + + return ( +
    + {width && } + {width && ( +
      + {[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((item, idx) => ( +
    • + +
    • + ))} +
    + )} +
    + ); +}; + +export default TrainAnimation; diff --git a/services/ahhachul.com/src/components/domain/home/stations/trainAnimation/TrainAnimation.styled.ts b/services/ahhachul.com/src/components/domain/home/stations/trainAnimation/TrainAnimation.styled.ts new file mode 100644 index 000000000..3d9e80b55 --- /dev/null +++ b/services/ahhachul.com/src/components/domain/home/stations/trainAnimation/TrainAnimation.styled.ts @@ -0,0 +1,28 @@ +import { Interpolation, Theme } from '@emotion/react'; + +import { mixins } from '@/styles'; + +export const trainCongestions = [ + mixins.posRel, + mixins.fullWidth, + mixins.flexCenterCenter, + { + '& > ul': { + display: 'grid', + gridTemplateColumns: 'repeat(10, 1fr)', + columnGap: '4px', + width: 'calc(100% - 32px)', + position: 'absolute', + top: '4px', + left: '50%', + transform: 'translateX(-50%)', + + '& > li': { + width: '100%', + height: '26px', + position: 'relative', + left: '8px', + }, + }, + }, +] as Interpolation; diff --git a/services/ahhachul.com/src/components/domain/home/stations/trainAnimation/TrainEachSection.component.tsx b/services/ahhachul.com/src/components/domain/home/stations/trainAnimation/TrainEachSection.component.tsx new file mode 100644 index 000000000..c1665c904 --- /dev/null +++ b/services/ahhachul.com/src/components/domain/home/stations/trainAnimation/TrainEachSection.component.tsx @@ -0,0 +1,9 @@ +import * as S from './TrainEachSection.styled'; + +interface TrainEachSectionProps { + roomNumber: number; +} + +export const TrainEachSection = ({ roomNumber }: TrainEachSectionProps) => ( +
    {roomNumber}
    +); diff --git a/services/ahhachul.com/src/components/domain/home/stations/trainAnimation/TrainEachSection.styled.ts b/services/ahhachul.com/src/components/domain/home/stations/trainAnimation/TrainEachSection.styled.ts new file mode 100644 index 000000000..3f2c9196d --- /dev/null +++ b/services/ahhachul.com/src/components/domain/home/stations/trainAnimation/TrainEachSection.styled.ts @@ -0,0 +1,11 @@ +import { Interpolation, Theme } from '@emotion/react'; + +export const eachSection = { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + width: '100%', + height: '90%', + borderRadius: '6px', + backgroundColor: '#ffffff', +} as Interpolation; diff --git a/services/ahhachul.com/src/components/domain/home/stations/trainAnimation/TrainIcon.component.tsx b/services/ahhachul.com/src/components/domain/home/stations/trainAnimation/TrainIcon.component.tsx new file mode 100644 index 000000000..12aa9e3c0 --- /dev/null +++ b/services/ahhachul.com/src/components/domain/home/stations/trainAnimation/TrainIcon.component.tsx @@ -0,0 +1,31 @@ +import { useMemo } from 'react'; + +interface TrainIconProps { + width: number; +} + +export const TrainIcon = ({ width }: TrainIconProps) => { + const d = useMemo( + () => + `M5.27831 7.78309C6.31333 3.23054 10.3608 0 15.0295 0H293C294.105 0 ${width} 0.895431 ${width} 2V29C295 30.1046 294.105 31 293 31H3.7586C1.83335 31 0.406436 29.2123 0.833246 27.3349L5.27831 7.78309Z`, + [width], + ); + + return ( + + + + + ); +}; diff --git a/services/ahhachul.com/src/components/domain/home/stations/trainArrivals/TrainArrivals.component.tsx b/services/ahhachul.com/src/components/domain/home/stations/trainArrivals/TrainArrivals.component.tsx new file mode 100644 index 000000000..96d85b4fa --- /dev/null +++ b/services/ahhachul.com/src/components/domain/home/stations/trainArrivals/TrainArrivals.component.tsx @@ -0,0 +1,100 @@ +import { memo, useEffect, useState } from 'react'; + +import { motion } from 'motion/react'; + +import { motions } from '@/constants'; +import type { ITrain } from '@/types'; +import { getRandomNumber1to60 } from '@/utils'; + +import * as S from './TrainArrivals.styled'; + +interface TrainArrivalTimesProps { + trainRealTimes: ITrain[]; +} + +const TrainArrivals = ({ trainRealTimes }: TrainArrivalTimesProps) => { + const [trainTimers, setTrainTimers] = useState<{ [key: string]: number }>({}); + + useEffect(() => { + const initialTimers: { [key: string]: number } = {}; + trainRealTimes.forEach(train => { + initialTimers[`train_${train.trainNum}`] = + train.currentArrivalTime * 60 - getRandomNumber1to60(); + }); + setTrainTimers(initialTimers); + }, [trainRealTimes]); + + const needTimer = Object.values(trainTimers).some(time => time > 0); + + useEffect(() => { + if (!needTimer) return; + + const timerId = setInterval(() => { + setTrainTimers(prev => { + const updated = { ...prev }; + let changed = false; + + Object.keys(updated).forEach(key => { + if (updated[key] > 0) { + updated[key] -= 1; + changed = true; + } + }); + + return changed ? updated : prev; + }); + }, 1000); + + return () => clearInterval(timerId); + }, [needTimer]); + + return ( + + {trainRealTimes?.map(train => ( + + ))} + + ); +}; + +const TrainCard = memo( + ({ train, remainingSeconds }: { train: ITrain; remainingSeconds: number }) => { + const formatTime = (seconds: number) => { + if (!seconds && seconds !== 0) return '์•Œ ์ˆ˜ ์—†์Œ'; + + if (seconds < 60) { + return '๊ณง ๋„์ฐฉ'; + } + + const minutes = Math.floor(seconds / 60); + const remainingSeconds = seconds % 60; + + if (remainingSeconds === 0) { + return `${minutes}๋ถ„`; + } + + return `${minutes}๋ถ„ ${remainingSeconds}์ดˆ`; + }; + + return ( +
  • + {train.destinationStationDirection} + {formatTime(remainingSeconds)} +
  • + ); + }, +); + +TrainCard.displayName = 'TrainCard'; + +export default TrainArrivals; diff --git a/services/ahhachul.com/src/components/domain/home/stations/trainArrivals/TrainArrivals.styled.ts b/services/ahhachul.com/src/components/domain/home/stations/trainArrivals/TrainArrivals.styled.ts new file mode 100644 index 000000000..e011532d6 --- /dev/null +++ b/services/ahhachul.com/src/components/domain/home/stations/trainArrivals/TrainArrivals.styled.ts @@ -0,0 +1,48 @@ +import { css } from '@emotion/react'; + +export const arrivalList = css` + width: 100%; + display: grid; + grid-template-columns: repeat(2, 1fr); + justify-items: center; + column-gap: 24px; + row-gap: 12px; + + & > li { + display: flex; + align-items: center; + justify-content: flex-start; + gap: 8px; + width: 100%; + transition: opacity 0.2s ease; + + &:active { + opacity: 0.7; + } + + & > b { + color: #ffffff; + font-size: 14px; + font-weight: normal; + flex-shrink: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 75%; + } + + &:first-of-type > b, + &:first-of-type > span { + font-weight: bold; + } + + & > span { + color: #00baf6; + font-size: 14px; + flex-shrink: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } +`; diff --git a/services/ahhachul.com/src/components/domain/home/stations/trainRealTimes/TrainRealTimes.component.tsx b/services/ahhachul.com/src/components/domain/home/stations/trainRealTimes/TrainRealTimes.component.tsx new file mode 100644 index 000000000..571b42873 --- /dev/null +++ b/services/ahhachul.com/src/components/domain/home/stations/trainRealTimes/TrainRealTimes.component.tsx @@ -0,0 +1,153 @@ +import { memo, useReducer } from 'react'; + +import { motion } from 'motion/react'; + +import { RetryIcon } from '@/assets/icons/system'; +import { UiComponent } from '@/components'; +import { getArrivalStatusText, isSubwayNeedAnimation, motions } from '@/constants'; +import { useFetchTrainInfo } from '@/services/subway'; +import { useFlow } from '@/stackflow'; +import { fade } from '@/styles'; +import { CurrentTrainArrivalType, SubwayLineType, UpDownType, WithSubwayStationId } from '@/types'; + +import * as S from './TrainRealTimes.styled'; + +import TrainArrivals from '../trainArrivals/TrainArrivals.component'; +import SubwayUpDownFilter from '../upDownFilter/UpDownFilter.component'; + +interface TrainRealTimesProps extends WithSubwayStationId { + stationName: string; + subwayLineId: SubwayLineType; +} + +const TrainRealTimes = ({ stationId, stationName, subwayLineId }: TrainRealTimesProps) => { + const { push } = useFlow(); + const { data, isFetching, isError, refetch } = useFetchTrainInfo({ + stationId, + subwayLineId, + }); + + const [sort, handleSort] = useReducer( + prev => (prev === UpDownType.UP ? UpDownType.DOWN : UpDownType.UP), + UpDownType.UP, + ); + + const filterdStationsData = { + ...data, + trainRealTimes: data?.trainRealTimes?.filter(item => item.upDownType === sort), + }; + const isServiceTerminated = filterdStationsData?.trainRealTimes?.length === 0; + const currentTrain = filterdStationsData?.trainRealTimes?.[0]; + + return ( +
    +
    +
    {stationName}
    + {currentTrain?.nextStationDirection?.replace('๋ฐฉ๋ฉด', ' ๋ฐฉ๋ฉด')} +
    +
    +
    + +
    + +
    + {isFetching ? ( +
    + ) : isError ? ( +
    ์ผ์‹œ์ ์ธ ์˜ค๋ฅ˜
    + ) : ( + (filterdStationsData?.trainRealTimes || []).length > 0 && ( + + ) + )} +
    +
    + +
    +
    +
    + ); +}; + +interface TrainArrivalStatusProps { + sort: UpDownType; + isError: boolean; + isFetching: boolean; + currentArrivalTime?: number; + isServiceTerminated: boolean; + destinationStationDirection?: string; + currentTrainArrivalCode?: CurrentTrainArrivalType; + onRefetch: () => void; + handleSort: () => void; +} + +const TrainArrivalStatus = memo( + ({ + sort, + isError, + isFetching, + isServiceTerminated, + currentTrainArrivalCode, + destinationStationDirection = '', + onRefetch, + handleSort, + }: TrainArrivalStatusProps) => { + return ( + +
    + + {isFetching && } + {!isFetching && + getArrivalStatusText(isError, isServiceTerminated, currentTrainArrivalCode)} + + {isError || isFetching ? '' : destinationStationDirection} +
    +
    + +
    + +
    + + ); + }, +); + +interface RefreshButtonProps { + onRefresh: () => void; +} + +const RefreshButton = ({ onRefresh }: RefreshButtonProps) => { + return ( + + ); +}; + +TrainArrivalStatus.displayName = 'TrainArrivalStatus'; + +export default memo(TrainRealTimes); diff --git a/services/ahhachul.com/src/components/domain/home/stations/trainRealTimes/TrainRealTimes.styled.ts b/services/ahhachul.com/src/components/domain/home/stations/trainRealTimes/TrainRealTimes.styled.ts new file mode 100644 index 000000000..cb6bf2558 --- /dev/null +++ b/services/ahhachul.com/src/components/domain/home/stations/trainRealTimes/TrainRealTimes.styled.ts @@ -0,0 +1,162 @@ +import { css, type Interpolation, type Theme } from '@emotion/react'; + +import { subwayLineHexColors } from '@/constants'; +import { fadeIn, mixins } from '@/styles'; +import type { SubwayLineType } from '@/types'; + +export const inner = { + position: 'relative', + width: 'calc(100% - 40px)', + borderRadius: '20px', + backgroundColor: 'rgba(255, 255, 255, 0.1)', + margin: '0 auto', +} as Interpolation; + +export const thickBorder = (subwayLineId: SubwayLineType) => + ({ + position: 'absolute', + left: '0', + top: '0', + width: '100%', + height: '30px', + borderTopLeftRadius: '20px', + borderTopRightRadius: '20px', + transition: 'background-color 0.4s ease-in-out', + backgroundColor: subwayLineHexColors(Number(subwayLineId)), + display: 'flex', + alignItems: 'center', + gap: '4px', + animation: `${fadeIn} 0.8s forwards`, + + '& > span': { + color: 'white', + fontSize: '14px', + fontWeight: 600, + }, + }) as Interpolation; + +export const stationName = (subwayLineId: SubwayLineType) => + [ + mixins.flexCenterCenter, + ({ colors: { white, black } }: Theme) => ({ + height: '36px', + padding: '6px 12px', + + borderRadius: '35px', + transition: 'border-color 0.4s ease-in-out', + border: `4px solid ${subwayLineHexColors(Number(subwayLineId))}`, + color: black, + backgroundColor: white, + + marginLeft: '20px', + + fontSize: '16px', + fontWeight: 600, + }), + ] as Interpolation; + +export const trainInfos = [ + mixins.posRel, + mixins.flexColumn, + { + justifyContent: 'flex-end', + padding: '47px 0 0 0', + }, +] as Interpolation; + +export const currentTrainArrivalInfo = { + minHeight: '24px', + marginBottom: '14px', + padding: '0 16px', +}; + +export const arrivalInfoLabel = [ + mixins.posRel, + mixins.flexAlignCenter, + mixins.flexJustifySpaceBetween, + ({ colors: { white, primary } }: Theme) => ({ + '& > div': { + '& > b': { + animation: `${fadeIn} 0.8s forwards`, + color: primary.primary, + fontSize: '20px', + fontWeight: 700, + marginRight: '6px', + }, + + '& > span': { + animation: `${fadeIn} 0.8s forwards`, + position: 'relative', + top: '0.5px', + color: white, + fontSize: '16px', + lineHeight: 1.5, + }, + }, + }), +] as Interpolation; + +export const refetchBtnCss = () => + ({ + '& > svg': { + position: 'relative', + top: '1px', + width: '16px', + height: '16px', + }, + }) as Interpolation; + +export const buttonWrap = [ + mixins.fullWidth, + mixins.flexCenterCenter, + { + backgroundColor: 'inherit', + padding: '0 16px 14px 16px', + }, +] as Interpolation; + +export const button = [ + mixins.fullWidth, + mixins.flexCenterCenter, + ({ colors: { white } }: Theme) => ({ + height: '34px', + borderRadius: '8px', + padding: '0 16px', + color: white, + backgroundColor: 'rgba(255,255,255,0.08)', + fontWeight: 500, + transition: 'all 100ms ease-out', + + '&:hover': { + transform: 'scale(1.02)', + backgroundColor: 'rgba(255, 255, 255, 0.15)', + }, + + '&:active': { + transform: 'scale(0.98)', + backgroundColor: 'rgba(255, 255, 255, 0.05)', + }, + }), +] as Interpolation; + +export const listWrap = { + position: 'relative', + padding: '14px 16px', + borderTop: '1px solid rgba(255, 255, 255, 0.12)', +} as Interpolation; + +export const loading = css` + animation: ios-spin 1.5s ease infinite; +`; + +export const upDown = css` + display: flex; + align-items: center; + gap: 8px; + + & > div { + width: 1px; + height: 8px; + background-color: #949db2; + } +`; diff --git a/services/ahhachul.com/src/components/domain/home/stations/upDownFilter/UpDownFilter.component.tsx b/services/ahhachul.com/src/components/domain/home/stations/upDownFilter/UpDownFilter.component.tsx new file mode 100644 index 000000000..c6cb09fad --- /dev/null +++ b/services/ahhachul.com/src/components/domain/home/stations/upDownFilter/UpDownFilter.component.tsx @@ -0,0 +1,20 @@ +import { TransferIcon } from '@/assets/icons/system'; +import { UpDownType } from '@/types'; + +import * as S from './UpDownFilter.styled'; + +interface SubwayUpDownFilterProps { + sort: UpDownType; + handleSort: () => void; +} + +const SubwayUpDownFilter = ({ sort, handleSort }: SubwayUpDownFilterProps) => { + return ( + + {sort === UpDownType.UP ? '์ƒํ–‰' : 'ํ•˜ํ–‰'} + + + ); +}; + +export default SubwayUpDownFilter; diff --git a/services/ahhachul.com/src/components/domain/home/stations/upDownFilter/UpDownFilter.styled.ts b/services/ahhachul.com/src/components/domain/home/stations/upDownFilter/UpDownFilter.styled.ts new file mode 100644 index 000000000..0c620f9c4 --- /dev/null +++ b/services/ahhachul.com/src/components/domain/home/stations/upDownFilter/UpDownFilter.styled.ts @@ -0,0 +1,21 @@ +import styled from '@emotion/styled'; + +export const FilterBtn = styled.button` + color: white; + font-size: 13px; + font-weight: 500; + display: flex; + background: inherit; + align-items: center; + justify-content: center; + gap: 2px; + border: 0; + outline: none; + padding: 0; + border-radius: 8px; + + & > svg { + width: 16px; + height: 16px; + } +`; diff --git a/services/ahhachul.com/src/components/domain/index.ts b/services/ahhachul.com/src/components/domain/index.ts index f90fbb347..35134ca66 100644 --- a/services/ahhachul.com/src/components/domain/index.ts +++ b/services/ahhachul.com/src/components/domain/index.ts @@ -2,3 +2,4 @@ export * as AuthComponent from './auth'; export * as HomeComponent from './home'; export * as LostFoundComponent from './lostFound'; export * as CommunityComponent from './community'; +export * as ComplaintComponent from './complaint'; diff --git a/services/ahhachul.com/src/components/domain/lostFound/postDetail/commentInput/CommentInput.component.tsx b/services/ahhachul.com/src/components/domain/lostFound/postDetail/commentInput/CommentInput.component.tsx new file mode 100644 index 000000000..d9d872d10 --- /dev/null +++ b/services/ahhachul.com/src/components/domain/lostFound/postDetail/commentInput/CommentInput.component.tsx @@ -0,0 +1,56 @@ +import { useQueryClient } from '@tanstack/react-query'; + +import { sleep } from '@ahhachul/utils'; + +import { UiComponent } from '@/components'; +import { usePostComment } from '@/services/comment'; +import { lostFoundKeys } from '@/services/lostFound'; + +type Props = { + id: number; +}; + +const LostFoundCommentInput = ({ id }: Props) => { + const queryClient = useQueryClient(); + const lostFoundCommentQueryKey = lostFoundKeys.comments(id); + const { mutate } = usePostComment(); + + const submitComment = ({ isPrivate, comment }: { isPrivate: boolean; comment: string }) => { + mutate( + { + postId: id, + content: comment, + upperCommentId: null, + isPrivate: isPrivate, + servicePath: 'lost-posts', + }, + { + onSuccess: async res => { + queryClient.invalidateQueries({ + queryKey: lostFoundCommentQueryKey, + }); + + await sleep(250); + + const comment = document.querySelector(`[data-comment-id="${res.result.id}"]`); + if (comment) { + comment.scrollIntoView({ + block: 'start', + behavior: 'smooth', + }); + } + }, + }, + ); + }; + + return ( + + ); +}; + +export default LostFoundCommentInput; diff --git a/services/ahhachul.com/src/components/domain/lostFound/postDetail/commentList/LostFoundCommentList.component.tsx b/services/ahhachul.com/src/components/domain/lostFound/postDetail/commentList/LostFoundCommentList.component.tsx index 7a05c0de5..200da1549 100644 --- a/services/ahhachul.com/src/components/domain/lostFound/postDetail/commentList/LostFoundCommentList.component.tsx +++ b/services/ahhachul.com/src/components/domain/lostFound/postDetail/commentList/LostFoundCommentList.component.tsx @@ -1,15 +1,16 @@ -import { BookmarkIcon } from '@/assets/icons/system'; +// import { BookmarkIcon } from '@/assets/icons/system'; import { UiComponent } from '@/components'; -import { useFetchLostFoundCommentList } from '@/services/lostFound'; +import { lostFoundKeys, useFetchLostFoundCommentList } from '@/services/lostFound'; import * as S from './LostFoundCommentList.styled'; interface LostFoundCommentListProps { id: number; commentCnt: number; + isArticleAuthor: boolean; } -const LostFoundCommentList = ({ commentCnt, id }: LostFoundCommentListProps) => { +const LostFoundCommentList = ({ commentCnt, id, isArticleAuthor }: LostFoundCommentListProps) => { return ( @@ -17,23 +18,33 @@ const LostFoundCommentList = ({ commentCnt, id }: LostFoundCommentListProps) => ๋Œ“๊ธ€ {commentCnt ?? 0} - + {/* */} } suspenseFallback={} > - + ); }; -const CommentListInner = ({ id }: Pick) => { +const CommentListInner = ({ + id, + isArticleAuthor, +}: Pick) => { const { data } = useFetchLostFoundCommentList(id); - return ; + return ( + + ); }; export default LostFoundCommentList; diff --git a/services/ahhachul.com/src/components/domain/lostFound/postDetail/error/LostFoundErrorPage.styled.tsx b/services/ahhachul.com/src/components/domain/lostFound/postDetail/error/LostFoundErrorPage.styled.tsx index 68360ffcc..13d16da0c 100644 --- a/services/ahhachul.com/src/components/domain/lostFound/postDetail/error/LostFoundErrorPage.styled.tsx +++ b/services/ahhachul.com/src/components/domain/lostFound/postDetail/error/LostFoundErrorPage.styled.tsx @@ -11,14 +11,16 @@ export const Container = styled.div` export const Title = styled.p` text-align: center; - font-size: 20px; - font-weight: 700; + ${({ theme }) => css` + ${theme.fonts.labelMedium}; + `} `; export const Description = styled.p` text-align: center; ${({ theme }) => css` - ${theme.fonts.labelMedium}; + ${theme.fonts.bodyMedium}; + color: ${theme.colors.gray[80]}; `} `; diff --git a/services/ahhachul.com/src/components/domain/lostFound/postDetail/headerActions/LostFoundHeaderActions.component.tsx b/services/ahhachul.com/src/components/domain/lostFound/postDetail/headerActions/LostFoundHeaderActions.component.tsx index 91cce3176..b48070861 100644 --- a/services/ahhachul.com/src/components/domain/lostFound/postDetail/headerActions/LostFoundHeaderActions.component.tsx +++ b/services/ahhachul.com/src/components/domain/lostFound/postDetail/headerActions/LostFoundHeaderActions.component.tsx @@ -1,25 +1,22 @@ -import { useReducer } from 'react'; - import { useActivity } from '@stackflow/react'; -import { MoreVerticalIcon, ShareIcon } from '@/assets/icons/system'; +import { ShareIcon } from '@/assets/icons/system'; import { UiComponent } from '@/components'; import { useNativeBridge } from '@/contexts'; -import { useUser } from '@/hooks/domain'; -import { useFlow } from '@/stackflow'; +import { lostFoundKeys } from '@/services/lostFound'; +import type { LostStatus } from '@/types'; import { getSharePageURL } from '@/utils/share'; import * as S from './LostFoundHeaderActions.styled'; interface LostFoundHeaderActionsProps { id: number; + status: LostStatus; createdBy: number; } -const LostFoundHeaderActions = ({ id, createdBy }: LostFoundHeaderActionsProps) => { - const { push } = useFlow(); - const { user } = useUser(); - const { isActive } = useActivity(); +const LostFoundHeaderActions = ({ id, status, createdBy }: LostFoundHeaderActionsProps) => { + const { params, isActive } = useActivity(); const { bridge, isBridgeInitialized } = useNativeBridge(); const handleClickShare = () => { @@ -29,51 +26,25 @@ const LostFoundHeaderActions = ({ id, createdBy }: LostFoundHeaderActionsProps) bridge.send.share(`${targetUrl}/${id}`); }; - const [isOpen, toggle] = useReducer(open => !open, false); - - const isAuthor = user?.memberId === createdBy; - - const actions = isAuthor - ? [ - { - label: '์ˆ˜์ •ํ•˜๊ธฐ', - onClick: () => push('EditLostFoundPage', { id }), - }, - { - label: '์‚ญ์ œํ•˜๊ธฐ', - onClick: () => console.log('์‚ญ์ œํ•˜๊ธฐ'), - }, - ] - : [ - { - label: '์‹ ๊ณ ํ•˜๊ธฐ', - onClick: () => console.log('์‹ ๊ณ ํ•˜๊ธฐ'), - }, - ]; - if (!isActive) return null; return ( - <> - -
    - - - - - - - - -
    -
    - - -
    - -
    -
    - + +
    + + + + + + +
    +
    ); }; diff --git a/services/ahhachul.com/src/components/domain/lostFound/postDetail/headerActions/LostFoundHeaderActions.styled.tsx b/services/ahhachul.com/src/components/domain/lostFound/postDetail/headerActions/LostFoundHeaderActions.styled.tsx index a5fce08de..8ca749392 100644 --- a/services/ahhachul.com/src/components/domain/lostFound/postDetail/headerActions/LostFoundHeaderActions.styled.tsx +++ b/services/ahhachul.com/src/components/domain/lostFound/postDetail/headerActions/LostFoundHeaderActions.styled.tsx @@ -1,7 +1,7 @@ import { css } from '@emotion/react'; import styled from '@emotion/styled'; -export const Container = styled.button` +export const Container = styled.section` ${({ theme }) => css` position: fixed; top: 0; diff --git a/services/ahhachul.com/src/components/domain/lostFound/postDetail/index.ts b/services/ahhachul.com/src/components/domain/lostFound/postDetail/index.ts index 7dc5eb2ab..6b956d316 100644 --- a/services/ahhachul.com/src/components/domain/lostFound/postDetail/index.ts +++ b/services/ahhachul.com/src/components/domain/lostFound/postDetail/index.ts @@ -4,3 +4,4 @@ export { default as LostFoundDetailSkeleton } from './skeleton/LostFoundDetail.s export { default as LostFoundCommentList } from './commentList/LostFoundCommentList.component'; export { default as RecommendPostList } from './recommendPostList/RecommendPostList.component'; export { default as LostFoundDetailHeaderActions } from './headerActions/LostFoundHeaderActions.component'; +export { default as CommentInput } from './commentInput/CommentInput.component'; diff --git a/services/ahhachul.com/src/components/domain/lostFound/postDetail/lost112InfoTable/Lost112InfoTable.component.tsx b/services/ahhachul.com/src/components/domain/lostFound/postDetail/lost112InfoTable/Lost112InfoTable.component.tsx index 43ebf386b..14fc488ed 100644 --- a/services/ahhachul.com/src/components/domain/lostFound/postDetail/lost112InfoTable/Lost112InfoTable.component.tsx +++ b/services/ahhachul.com/src/components/domain/lostFound/postDetail/lost112InfoTable/Lost112InfoTable.component.tsx @@ -12,6 +12,12 @@ interface Props { const Lost112InfoTable = ({ post }: Props) => { const { bridge, isBridgeInitialized } = useNativeBridge(); + const handleClickExternalPhone = () => { + if (!isBridgeInitialized) return; + + bridge.send.callPhone(post.storageNumber); + }; + const handleClickExternalLink = () => { if (!isBridgeInitialized) return; @@ -27,13 +33,6 @@ const Lost112InfoTable = ({ post }: Props) => { ์Šต๋“์ผ {formatDateTime(post.createdAt, { format: 'short' })} - {post?.storage && ( - <> - ์Šต๋“์žฅ์†Œ - {post.storage} - - )} - {post?.categoryName && ( <> ๋ฌผํ’ˆ๋ถ„๋ฅ˜ @@ -41,10 +40,10 @@ const Lost112InfoTable = ({ post }: Props) => { )} - {post?.storageNumber && ( + {post?.storage && ( <> - ๋ณด๊ด€ ์žฅ์†Œ ์ „ํ™”๋ฒˆํ˜ธ - {post.storageNumber} + ์Šต๋“์žฅ์†Œ + {post.storage} )} @@ -55,6 +54,13 @@ const Lost112InfoTable = ({ post }: Props) => { )} + {post?.storageNumber && ( + <> + ๋ณด๊ด€ ์žฅ์†Œ ์ „ํ™”๋ฒˆํ˜ธ + {post.storageNumber} + + )} + {post?.pageUrl && ( <> ์›๋ณธ ๊ฒŒ์‹œ๊ธ€ diff --git a/services/ahhachul.com/src/components/domain/lostFound/postDetail/lost112InfoTable/Lost112InfoTable.styled.tsx b/services/ahhachul.com/src/components/domain/lostFound/postDetail/lost112InfoTable/Lost112InfoTable.styled.tsx index 3e61622fa..39720dd33 100644 --- a/services/ahhachul.com/src/components/domain/lostFound/postDetail/lost112InfoTable/Lost112InfoTable.styled.tsx +++ b/services/ahhachul.com/src/components/domain/lostFound/postDetail/lost112InfoTable/Lost112InfoTable.styled.tsx @@ -10,7 +10,7 @@ export const TableWrapper = styled.div` export const ContentWrapper = styled.div` background-color: ${({ theme }) => theme.colors.white}; border-radius: 8px; - padding: 16px 20px; + padding: 20px; `; export const Title = styled.h2` @@ -26,8 +26,9 @@ export const GridContainer = styled.div` display: grid; grid-template-columns: 120px 1fr; gap: 16px; - padding: 12px 5px; + padding: 8px 4px; font-size: 14px; + margin-top: 12px; `; export const Label = styled.div` @@ -44,20 +45,19 @@ export const Value = styled.div` `} `; -export const StyledLink = styled.button` - ${({ theme }) => css` - ${theme.fonts.labelMedium}; - color: #1d4ed8; - `} +export const StyledLink = styled(Value)` + color: #1d4ed8; `; export const StatusWrapper = styled.div` - margin-top: 24px; + margin-top: 8px; + padding-top: 20px; display: flex; align-items: center; gap: 8px; justify-content: center; color: ${({ theme }) => theme.colors.green[600]}; + border-top: ${({ theme }) => `1px solid ${theme.colors.gray[20]}`}; `; export const StatusDot = styled.div` diff --git a/services/ahhachul.com/src/components/domain/lostFound/postDetail/recommendPostList/listItem/ListItem.component.tsx b/services/ahhachul.com/src/components/domain/lostFound/postDetail/recommendPostList/listItem/ListItem.component.tsx index 3c3417fd1..e80e1a7ad 100644 --- a/services/ahhachul.com/src/components/domain/lostFound/postDetail/recommendPostList/listItem/ListItem.component.tsx +++ b/services/ahhachul.com/src/components/domain/lostFound/postDetail/recommendPostList/listItem/ListItem.component.tsx @@ -1,5 +1,6 @@ import { formatDateTime } from '@ahhachul/utils'; +import { StackFlow } from '@/stackflow'; import type { RecommendPost as TypeRecommendPost } from '@/types'; import * as S from './ListItem.styled'; @@ -10,33 +11,35 @@ interface RecommendPostProps { const RecommendPost = ({ post }: RecommendPostProps) => { return ( - - - - - {post.title} - - - LOST112 - - {formatDateTime(post.createdAt, { format: 'relative' })} - - - - {post?.imageUrl && ( - - - - )} - - - + + + + + + {post.title} + + + LOST112 + + {formatDateTime(post.createdAt, { format: 'relative' })} + + + + {post?.imageUrl && ( + + + + )} + + + + ); }; diff --git a/services/ahhachul.com/src/components/domain/lostFound/postDetail/template/LostFoundDetail.component.tsx b/services/ahhachul.com/src/components/domain/lostFound/postDetail/template/LostFoundDetail.component.tsx index dd90947ff..75a83f57e 100644 --- a/services/ahhachul.com/src/components/domain/lostFound/postDetail/template/LostFoundDetail.component.tsx +++ b/services/ahhachul.com/src/components/domain/lostFound/postDetail/template/LostFoundDetail.component.tsx @@ -1,7 +1,12 @@ +import { LazyLoadImage } from 'react-lazy-load-image-component'; + +import { motion } from 'motion/react'; + import { formatDateTime, getRandomNumber } from '@ahhachul/utils'; import { LostFoundComponent, UiComponent } from '@/components'; import { subwayIconMap } from '@/constants'; +import { useUser } from '@/hooks/domain'; import { useFetchLostFoundDetail } from '@/services/lostFound'; import { formatLost112Content } from '@/utils'; import { isLexicalContent } from '@/utils/lexical'; @@ -18,6 +23,9 @@ interface LostFoundDetailProps { const LostFoundDetail = ({ id }: LostFoundDetailProps) => { const { data: post } = useFetchLostFoundDetail(id); + const { user } = useUser(); + const isArticleAuthor = +post.createdBy === user?.memberId; + const images = post.isFromLost112 ? [ { @@ -29,9 +37,57 @@ const LostFoundDetail = ({ id }: LostFoundDetailProps) => { return ( <> - + + {post.status === 'COMPLETE' && ( + + + + + + + ์ด ๊ฒŒ์‹œ๊ธ€์˜ ๋ถ„์‹ค๋ฌผ์€ ์ฐพ๊ธฐ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค + + + )} + @@ -48,14 +104,14 @@ const LostFoundDetail = ({ id }: LostFoundDetailProps) => { {post.isFromLost112 && ( <> - {/* lost112-image */} + ๋กœ์ŠคํŠธ 112์— ๋“ฑ๋ก๋œ ๋ถ„์‹ค๋ฌผ์ž…๋‹ˆ๋‹ค. )} - + {post.isFromLost112 || !isLexicalContent(post.content) ? ( {formatLost112Content(post.content)} ) : ( @@ -66,8 +122,14 @@ const LostFoundDetail = ({ id }: LostFoundDetailProps) => { + - + + ); }; diff --git a/services/ahhachul.com/src/components/domain/lostFound/postDetail/template/LostFoundDetail.styled.tsx b/services/ahhachul.com/src/components/domain/lostFound/postDetail/template/LostFoundDetail.styled.tsx index 19bae0126..0ac53767d 100644 --- a/services/ahhachul.com/src/components/domain/lostFound/postDetail/template/LostFoundDetail.styled.tsx +++ b/services/ahhachul.com/src/components/domain/lostFound/postDetail/template/LostFoundDetail.styled.tsx @@ -1,10 +1,11 @@ import { css } from '@emotion/react'; import styled from '@emotion/styled'; +import { motion } from 'motion/react'; export const ArticleWrapper = styled.article``; export const ContentWrapper = styled.div` - padding: 20px 20px 24px; + padding: 20px 20px 0 20px; `; export const TitleWrapper = styled.div` @@ -43,6 +44,7 @@ export const AuthorText = styled.span` `; export const DateText = styled.span` + font-size: 11px; color: ${({ theme }) => theme.colors.gray[70]}; `; @@ -72,14 +74,35 @@ export const Lost112Text = styled.span` `} `; -export const ContentContainer = styled.div` - padding: 0 20px; +export const CompleteWrapper = styled(motion.div)` + ${({ theme }) => css` + padding: 0 20px; + display: flex; + align-items: center; + height: 48px; + width: 100%; + gap: 4px; + background-color: ${theme.colors['key-color']}; + `} `; -export const TextContent = styled.pre` +export const CompleteText = styled.span` + ${({ theme }) => css` + ${theme.fonts.labelMedium}; + color: ${theme.colors.white}; + `} +`; + +export const ContentContainer = styled.div<{ isFromLost112: boolean }>` + ${({ isFromLost112 }) => css` + padding: ${isFromLost112 ? '0 20px' : '24px 20px 0'}; + `} +`; + +export const TextContent = styled.div` ${({ theme }) => css` ${theme.fonts.bodyLargeSemi}; - font-family: Pretendard; + font-family: 'Pretendard'; color: ${theme.colors.gray[90]}; padding: 24px 0; margin-top: 12px; @@ -100,3 +123,8 @@ export const LexicalContent = styled.div` } } `; + +export const Padding = styled.div` + width: 100%; + height: 194px; +`; diff --git a/services/ahhachul.com/src/components/domain/lostFound/searchResults/skeleton/SearchedList.skeleton.styled.tsx b/services/ahhachul.com/src/components/domain/lostFound/searchResults/skeleton/SearchedList.skeleton.styled.tsx index 61a495ed9..7dcc228e5 100644 --- a/services/ahhachul.com/src/components/domain/lostFound/searchResults/skeleton/SearchedList.skeleton.styled.tsx +++ b/services/ahhachul.com/src/components/domain/lostFound/searchResults/skeleton/SearchedList.skeleton.styled.tsx @@ -8,7 +8,7 @@ interface SectionWrapperProps { } export const SectionWrapper = styled.section` padding-top: 99px; - transform: ${({ isScale }) => (isScale ? 'translateY(-50px)' : 'translateY(0)')} + transform: ${({ isScale }) => (isScale ? 'translateY(-50px)' : 'translateY(0)')}; opacity: 0; animation: fadeIn 0.5s ease-in-out forwards; @@ -27,7 +27,7 @@ export const SectionWrapper = styled.section` `; export const ArticleItem = styled.article<{ delay: number }>` - padding: 24px 20px; + padding: 14px 20px; opacity: 0; animation: fadeIn 0.5s ease-in-out forwards; border-bottom: 1px solid ${({ theme }) => theme.colors.gray[20]}; diff --git a/services/ahhachul.com/src/components/domain/lostFound/searchResults/skeleton/SearchedList.skeleton.tsx b/services/ahhachul.com/src/components/domain/lostFound/searchResults/skeleton/SearchedList.skeleton.tsx index b194ffe97..91949a628 100644 --- a/services/ahhachul.com/src/components/domain/lostFound/searchResults/skeleton/SearchedList.skeleton.tsx +++ b/services/ahhachul.com/src/components/domain/lostFound/searchResults/skeleton/SearchedList.skeleton.tsx @@ -14,8 +14,8 @@ const SearchedListSkeleton = ({ isScale }: SearchedListSkeletonProps) => { - - + + {/* ์ด๋ฏธ์ง€ ์Šค์ผˆ๋ ˆํ†ค */} diff --git a/services/ahhachul.com/src/components/domain/my/MenuSections.component.tsx b/services/ahhachul.com/src/components/domain/my/MenuSections.component.tsx new file mode 100644 index 000000000..7526cfdc9 --- /dev/null +++ b/services/ahhachul.com/src/components/domain/my/MenuSections.component.tsx @@ -0,0 +1,115 @@ +import { css } from '@emotion/react'; +import styled from '@emotion/styled'; + +import { ChevronIcon } from '@/assets/icons/system'; +import { useToast } from '@/hooks/useToast'; + +import deps from '../../../../package.json'; + +const menuSections = [ + { label: '์•Œ๋ฆผ ์„ค์ •', to: '/all/settings/notifications' }, + { label: '์•ฝ๊ด€ ๋ฐ ์ด์šฉ ๋™์˜', to: '/all/terms' }, +]; + +const MenuSections = () => { + const { addToast } = useToast(); + + const showToast = () => addToast('์ค€๋น„์ค‘์ธ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค.', 'info'); + + // const { handleClickOpenKakao } = useKakaoChannel(); + + return ( + + + {menuSections.map((section, index) => ( +
    +

    {section.label}

    + +
    + ))} + +
    +

    ์•ฑ ๋ฒ„์ „

    +

    {deps.version}

    +
    +
    + + +

    + ๊ณ ๊ฐ์„ผํ„ฐ +

    +

    + ์„œ๋น„์Šค ๊ด€๋ จ ๋ฌธ์˜๋Š” ์•„ํ•˜์ฒ  ์นด์นด์˜ค ๊ณ ๊ฐ์„ผํ„ฐ ์ฑ—๋ด‡์„ ์ด์šฉํ•ด์ฃผ์„ธ์š” +

    + +
    +
    +
    + ); +}; + +const SectionsList = styled.div` + display: flex; + flex-direction: column; + margin-top: 16px; + + & > div > h4 { + font-weight: 500; + } +`; + +const SectionsBox = styled.div` + & + & { + margin-top: 16px; + } +`; + +const Section = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + height: 56px; + + p { + font-size: 16px; + line-height: 24px; + font-weight: 500; + color: #26282b; + } + + & > svg { + transform: rotate(270deg); + } +`; + +const SectionCs = styled.div` + padding: 16px 0; +`; + +export default MenuSections; diff --git a/services/ahhachul.com/src/components/domain/my/RequestCard.component.tsx b/services/ahhachul.com/src/components/domain/my/RequestCard.component.tsx new file mode 100644 index 000000000..6bef7ee4f --- /dev/null +++ b/services/ahhachul.com/src/components/domain/my/RequestCard.component.tsx @@ -0,0 +1,68 @@ +import styled from '@emotion/styled'; + +import AppLinkIcon from '@/assets/icons/my/app-link.svg'; +import QnaIcon from '@/assets/icons/my/qna.svg'; +import { useToast } from '@/hooks/useToast'; +import { useFlow } from '@/stackflow'; + +const RequestCard = () => { + const { push } = useFlow(); + const { addToast } = useToast(); + + const handleSettingPage = () => push('SettingPage', {}); + const showToast = () => addToast('์ค€๋น„์ค‘์ธ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค.', 'info'); + + return ( + + + +
    + + + + ); +}; + +const Wrapper = styled.div` + display: flex; + flex-direction: row; + justify-content: space-evenly; + align-items: center; + width: 100%; + height: 64px; + background-color: #f3f7ff; + padding: 4px 0; + border-radius: 8px; + + button { + display: flex; + flex-direction: row; + align-items: center; + outline: none; + border: none; + background-color: inherit; + } + + div.divider { + width: 1px; + height: 24px; + background: #d7dfef; + border-radius: 2px; + } + + p { + font-size: 16px; + line-height: 20px; + font-weight: 600; + margin-left: 10px; + color: #3c3f44; + } +`; + +export default RequestCard; diff --git a/services/ahhachul.com/src/components/domain/my/UserProfile.component.tsx b/services/ahhachul.com/src/components/domain/my/UserProfile.component.tsx new file mode 100644 index 000000000..8828199d1 --- /dev/null +++ b/services/ahhachul.com/src/components/domain/my/UserProfile.component.tsx @@ -0,0 +1,69 @@ +import styled from '@emotion/styled'; + +import { ChevronIcon } from '@/assets/icons/system'; +import { Avatar } from '@/components/common/avatar/Avatar.component'; +import { useAuth } from '@/contexts'; +import { useFetchUserProfile } from '@/services/user'; +import { useFlow } from '@/stackflow'; + +const UserProfile = () => { + const { push } = useFlow(); + const { isCheckingAuthState } = useAuth(); + const { data: userInfo, isLoading } = useFetchUserProfile(); + + if (isLoading || isCheckingAuthState) return null; + + return ( + push('MyAccountPage', {})}> + + + +
    +

    {userInfo?.result?.nickname || '์•„ํ•˜์ฒ '}

    +

    {userInfo?.result?.email}

    +
    +
    + + +
    + ); +}; + +const Wrapper = styled.div` + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + width: 100%; + margin: 16px 0; + + & > svg { + transform: rotate(270deg); + } +`; + +const UserInfo = styled.div` + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + + img { + margin-right: 12px; + } + + p.name { + font-size: 18px; + line-height: 24px; + font-weight: 600; + color: #26282b; + } + p.mobile { + font-size: 14px; + line-height: 18px; + color: #9a9ea8; + opacity: 0.8; + } +`; + +export default UserProfile; diff --git a/services/ahhachul.com/src/components/layout/BaseLayout.styled.tsx b/services/ahhachul.com/src/components/layout/BaseLayout.styled.tsx index 762d55d7d..72ad8815d 100644 --- a/services/ahhachul.com/src/components/layout/BaseLayout.styled.tsx +++ b/services/ahhachul.com/src/components/layout/BaseLayout.styled.tsx @@ -19,5 +19,5 @@ export const Scrollable = styled.div` ${mixins.overflowYScroll} padding-bottom: ${({ theme, navigationSlot }) => - navigationSlot ? theme.size.navbar.height_m : 0}; + navigationSlot ? `calc(${theme.size.navbar.height_m} + 32px)` : 0}; `; diff --git a/services/ahhachul.com/src/components/layout/ComposedLayout.component.tsx b/services/ahhachul.com/src/components/layout/ComposedLayout.component.tsx index ada82fc8b..377a3c67c 100644 --- a/services/ahhachul.com/src/components/layout/ComposedLayout.component.tsx +++ b/services/ahhachul.com/src/components/layout/ComposedLayout.component.tsx @@ -7,14 +7,22 @@ import * as S from './ComposedLayout.styled'; interface ComposedLayoutProps extends ComponentProps { children: ReactNode; - outerChildren: ReactNode; + outerChildren?: ReactNode; + navigationSlot?: boolean; + shouldShowBackground?: boolean; } -const ComposedLayout = ({ children, outerChildren, ...props }: ComposedLayoutProps) => { +const ComposedLayout = ({ + children, + outerChildren, + navigationSlot = true, + shouldShowBackground = true, + ...props +}: ComposedLayoutProps) => { return ( - + {outerChildren} - + {children} diff --git a/services/ahhachul.com/src/constants/community.ts b/services/ahhachul.com/src/constants/community.ts index cea5ac598..f153da62c 100644 --- a/services/ahhachul.com/src/constants/community.ts +++ b/services/ahhachul.com/src/constants/community.ts @@ -6,6 +6,7 @@ export const communityFilterKeys = { } as const; export const defaultCommunityFilterValues = { + hashTag: '', communityType: CommunityType.HOT, subwayLineId: SubwayLineFilterOptions.ALL_LINES, } as const; @@ -13,12 +14,12 @@ export const defaultCommunityFilterValues = { export const communityTypeOptions = { [CommunityType.HOT]: '์ธ๊ธฐ', [CommunityType.FREE]: '์ž์œ ', - [CommunityType.HUMOR]: '์œ ๋จธ', + [CommunityType.ISSUE]: '์ด์Šˆ', [CommunityType.INSIGHT]: '์ •๋ณด', } as const; export const communityTypeFormOptions = { [CommunityType.FREE]: '์ž์œ ', - [CommunityType.HUMOR]: '์œ ๋จธ', + [CommunityType.ISSUE]: '์ด์Šˆ', [CommunityType.INSIGHT]: '์ •๋ณด', } as const; diff --git a/services/ahhachul.com/src/constants/complaint.tsx b/services/ahhachul.com/src/constants/complaint.tsx index 5e71b01fd..65abc5931 100644 --- a/services/ahhachul.com/src/constants/complaint.tsx +++ b/services/ahhachul.com/src/constants/complaint.tsx @@ -1,8 +1,18 @@ +import { + ArrowMiniIcon, + EmergencyIcon, + HitIcon, + MetroIcon, + TreeIcon, +} from '@/assets/icons/complaint'; +import { SubwayLineFilterOptions } from '@/types'; +import type { ComplaintType } from '@/types/complaint'; + export const complaintsContentList = { ENVIRONMENTAL_COMPLAINT: { label: 'ํ™˜๊ฒฝ๋ฏผ์›', desc: 'ํ† ์‚ฌ๋ฌผ, ์˜ค๋ฌผ, ํ™˜๊ธฐ', - // icon: <ํ™˜๊ฒฝIcon />, + icon: , }, TEMPERATURE_CONTROL: { label: '์˜จ๋„์กฐ์ ˆ', @@ -12,26 +22,116 @@ export const complaintsContentList = { DISORDER: { label: '์งˆ์„œ์ €ํ•ด', desc: '์ทจ๊ฐ, ๋…ธ์ˆ™, ๊ตฌ๊ฑธ ๋“ฑ', - // icon: <์งˆ์„œIcon />, + icon: , }, ANNOUNCEMENT: { label: '์•ˆ๋‚ด๋ฐฉ์†ก', desc: '๋ฐฉ์†ก๋ถˆ๋Ÿ‰, ์Œ๋Ÿ‰ ์กฐ์ ˆ๊นŒ์ง€ ํ•œ ๋ฒˆ์—', - // icon: <ํ™”์‚ดํ‘œIcon />, + icon: , }, EMERGENCY_PATIENT: { label: '์‘๊ธ‰ํ™˜์ž', desc: 'ํ™˜์ž ๊ธด๊ธ‰ ์‹ ๊ณ ', - // icon: <์‘๊ธ‰Icon />, + icon: , }, VIOLENCE: { label: 'ํญ๋ ฅ', desc: '์—ด์ฐจ ๋‚ด ํญํ–‰์‹ ๊ณ ', - // icon: <ํญ๋ ฅIcon />, + icon: , }, SEXUAL_HARASSMENT: { label: '์„ฑ์ถ”ํ–‰', desc: '์—ด์ฐจ ๋‚ด ์„ฑํญ๋ ฅ, ๋ชฐ๋ž˜์นด๋ฉ”๋ผ', - // icon: <ํ™”์‚ดํ‘œIcon />, + icon: , }, }; + +export const complaintsContentDetail = { + ENVIRONMENTAL_COMPLAINT: { + title: '๋ฏผ์›์œ ํ˜• ์„ ํƒ', + selectList: { + WASTE: '์˜ค๋ฌผ', + VOMIT: 'ํ† ์‚ฌ๋ฌผ', + VENTILATION_REQUEST: 'ํ™˜๊ธฐ์š”์ฒญ', + }, + }, + TEMPERATURE_CONTROL: { + title: '์˜จ๋„์กฐ์ ˆ', + selectList: { + TOO_HOT: '๋”์›Œ์š”', + TOO_COLD: '์ถ”์›Œ์š”', + }, + }, + DISORDER: { + title: '๋ฏผ์›์œ ํ˜• ์„ ํƒ', + selectList: { + MOBILE_VENDOR: '์ด๋™์ƒ์ธ', + DRUNK: '์ทจ๊ฐ', + HOMELESS: '๋…ธ์ˆ™', + BEGGING: '๊ตฌ๊ฑธ', + RELIGIOUS_ACTIVITY: '์ข…๊ตํ–‰์œ„', + }, + }, + ANNOUNCEMENT: { + title: '์•ˆ๋‚ด๋ฐฉ์†ก', + selectList: { + NOISY: '์‹œ๋„๋Ÿฌ์›Œ์š”', + NOT_HEARD: '์•ˆ๋“ค๋ ค์š”', + }, + }, + EMERGENCY_PATIENT: { + title: '์‘๊ธ‰ํ™˜์ž์™€ ์–ด๋–ค ๊ด€๊ณ„์ด์‹ ๊ฐ€์š”?', + selectList: { + SELF: '๋ณธ์ธ', + WITNESS: '๋ชฉ๊ฒฉ์ž', + }, + }, + VIOLENCE: { + title: 'ํญ๋ ฅ', + selectList: { + VICTIM: 'ํ”ผํ•ด์ž', + WITNESS: '๋ชฉ๊ฒฉ์ž', + }, + }, + SEXUAL_HARASSMENT: { + title: '์„ฑ์ถ”ํ–‰', + selectList: { + VICTIM: 'ํ”ผํ•ด์ž', + WITNESS: '๋ชฉ๊ฒฉ์ž', + }, + }, +} as const; + +export const compalintFilterKeys = { + subwayLineId: 'subwayLineId', +} as const; + +export const defaultComplaintFilterValues = { + subwayLineId: SubwayLineFilterOptions.ALL_LINES, +} as const; + +export const complaintTypeOptions: Record = { + ENVIRONMENTAL_COMPLAINT: 'ํ™˜๊ฒฝ๋ฏผ์›', + TEMPERATURE_CONTROL: '์˜จ๋„์กฐ์ ˆ', + DISORDER: '์งˆ์„œ์ €ํ•ด', + ANNOUNCEMENT: '์•ˆ๋‚ด๋ฐฉ์†ก', + EMERGENCY_PATIENT: '์‘๊ธ‰ํ™˜์ž', + VIOLENCE: 'ํญ๋ ฅ', + SEXUAL_HARASSMENT: '์„ฑ์ถ”ํ–‰', +}; + +export const getSubwayComplaintCallNumber = (subwayLineId: number) => { + if (subwayLineId === 9) { + return '1544-4009'; + } + + if (subwayLineId === 18) { + return '031-8018-7777'; + } + + if (subwayLineId === 13 || subwayLineId === 11 || subwayLineId === 16) { + return '1544-7769'; + } + + return '1577-1234'; +}; diff --git a/services/ahhachul.com/src/constants/filter.ts b/services/ahhachul.com/src/constants/filter.ts index 4517a1ed0..00eaaed77 100644 --- a/services/ahhachul.com/src/constants/filter.ts +++ b/services/ahhachul.com/src/constants/filter.ts @@ -1,6 +1,8 @@ -import { AppUniqueFilterId } from '@/types/filter'; +import type { AppUniqueFilterId } from '@/types/filter'; export const APP_UNIQUE_FILTER_ID_LIST: Record = { CommunityPage: 'CommunityPage', LostFoundPage: 'LostFoundPage', + ComplaintPage: 'ComplaintPage', + HashtagPage: 'HashtagPage', } as const; diff --git a/services/ahhachul.com/src/constants/motion.ts b/services/ahhachul.com/src/constants/motion.ts index f6d986555..4a10797ed 100644 --- a/services/ahhachul.com/src/constants/motion.ts +++ b/services/ahhachul.com/src/constants/motion.ts @@ -1,6 +1,20 @@ export const defaultEasing = [0.6, -0.05, 0.01, 0.99]; export const motions = { + fadeIn: (duration = 0.3) => ({ + initial: { + opacity: 0, + transition: { duration, ease: defaultEasing }, + }, + animate: { + opacity: 1, + transition: { duration, ease: defaultEasing }, + }, + exit: { + opacity: 0, + transition: { duration, ease: defaultEasing }, + }, + }), fadeInAndUp: (duration = 0.3) => ({ initial: { opacity: 0, diff --git a/services/ahhachul.com/src/constants/path.ts b/services/ahhachul.com/src/constants/path.ts index e9644d061..073234eb9 100644 --- a/services/ahhachul.com/src/constants/path.ts +++ b/services/ahhachul.com/src/constants/path.ts @@ -24,7 +24,7 @@ export const PATH = { /** ๋ฏผ์› */ complaint: { home: '/complaint', - list: '/complaint/list', + list: '/complaint/panel', detail: '/complaint/:id', new: '/complaint/new', edit: '/complaint/:id/edit', @@ -34,6 +34,7 @@ export const PATH = { me: { home: '/me', setting: '/me/setting', + account: '/me/setting/account', }, /** ์ธ์ฆ ๊ด€๋ จ */ @@ -66,6 +67,23 @@ export const PATH = { edit: '/comments/:commentId/edit', reply: '/comments/:commentId/reply', }, + + /** ๋‰ด์Šค */ + news: { + detail: '/news/:newsId', + }, + + /** ํ•ด์‹œํƒœ๊ทธ */ + hashtag: { + home: '/hashtag', + }, + + /** ์ง€ํ•˜์ฒ  */ + subway: { + home: '/subway', + map: '/subway/map', + timeline: '/subway/timeline', + }, } as const; export const MAIN_PATHS: KeyOf[] = [ diff --git a/services/ahhachul.com/src/constants/subway.tsx b/services/ahhachul.com/src/constants/subway.tsx index 178543ac2..5ab5b99cc 100644 --- a/services/ahhachul.com/src/constants/subway.tsx +++ b/services/ahhachul.com/src/constants/subway.tsx @@ -1,17 +1,23 @@ /* eslint-disable react/jsx-key */ import * as Icons from '@/assets/icons/subway'; -import { SubwayLineFilterOptions, SubwayLineKrType, SubwayLineType } from '@/types'; +import { + CurrentTrainArrivalType, + SubwayLineFilterOptions, + type SubwayLineKrType, + type SubwayLineType, + type UserStationList, +} from '@/types'; export const subwayIconMap = new Map([ - [1, ], - [2, ], - [3, ], - [4, ], - [5, ], - [6, ], - [7, ], - [8, ], - [9, ], + [1, ], + [2, ], + [3, ], + [4, ], + [5, ], + [6, ], + [7, ], + [8, ], + [9, ], [10, ], [11, ], [12, ], @@ -40,9 +46,96 @@ export const subwayLineOptions: Record = { '16': '์ˆ˜์ธ๋ถ„๋‹น์„ ', '11': '๊ฒฝ์˜์ค‘์•™์„ ', '20': '์šฐ์ด์‹ ์„ค๊ฒฝ์ „์ฒ ', -} as const; +}; export const subwayLineFilterOptions = { [SubwayLineFilterOptions.ALL_LINES]: '์ „์ฒด ํ˜ธ์„  ๋ณด๊ธฐ', [SubwayLineFilterOptions.ONLY_MY_LINE]: '๋‚ด ํ˜ธ์„ ๋งŒ ๋ณด๊ธฐ', } as const; + +export const trainArrivalCodeMap: Record = { + ENTER: '์ง„์ž…', + ARRIVE: '๋„์ฐฉ', + DEPARTURE: '์ถœ๋ฐœ', + BEFORE_STATION_DEPARTURE: '์ „์—ญ์ถœ๋ฐœ', + BEFORE_STATION_ARRIVE: '์ „์—ญ๋„์ฐฉ', + BEFORE_STATION_ENTER: '์ „์—ญ์ง„์ž…', + RUNNING: '์šดํ–‰์ค‘', +} as const; + +export const subwayLineHexColors = (line: number) => { + switch (line) { + case 1: + return '#0052A4'; + case 2: + return '#00A84D'; + case 3: + return '#EF7C1C'; + case 4: + return '#00A4E3'; + case 5: + return '#996cac'; + case 6: + return '#CD7C2F'; + case 7: + return '#747F00'; + case 8: + return '#E6186C'; + case 9: + return '#BDB092'; + case 10: + return '#0054a6'; + case 11: + return '#77C4A3'; + case 12: + return '#0054a6'; + case 13: + return '#0090D2'; + case 14: + return '#6789CA'; + case 15: + return '#0054A6'; + case 16: + return '#FABE00'; + case 18: + return '#D31145'; + case 20: + return '#B7C450'; + default: + return 'rgba(255, 255, 255, 0.04)'; + } +}; + +export const defaultStationList: UserStationList = [ + { + label: 'ํšŒ์‚ฌ', + stationId: 557, + stationName: '๊ฐ•๋‚จ', + subwayLineInfoList: [ + { + subwayLineId: '2', + subwayLineName: '2ํ˜ธ์„ ', + }, + { + subwayLineId: '18', + subwayLineName: '์‹ ๋ถ„๋‹น์„ ', + }, + ], + }, +]; + +export const isSubwayNeedAnimation = (currentTrainArrivalCode?: string) => + ['ENTER', 'ARRIVE', 'BEFORE_STATION_DEPARTURE'].includes(currentTrainArrivalCode || ''); + +export const getArrivalStatusText = ( + isError: boolean, + isServiceTerminated: boolean, + currentTrainArrivalCode?: CurrentTrainArrivalType, +) => + isError + ? '์ผ์‹œ์ ์ธ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.' + : isServiceTerminated + ? '์šดํ–‰์ด ์ข…๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.' + : currentTrainArrivalCode + ? trainArrivalCodeMap[currentTrainArrivalCode] || '' + : '์ผ์‹œ์ ์ธ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.'; diff --git a/services/ahhachul.com/src/constants/toast.ts b/services/ahhachul.com/src/constants/toast.ts new file mode 100644 index 000000000..2dafc9bb0 --- /dev/null +++ b/services/ahhachul.com/src/constants/toast.ts @@ -0,0 +1,19 @@ +export const TOAST_MSG = { + SUCCESS: { + CREATE: '๊ธ€ ๋“ฑ๋ก ์„ฑ๊ณต', + UPDATE: '๊ธ€ ์ˆ˜์ • ์„ฑ๊ณต', + DELETE: '๊ธ€ ์‚ญ์ œ ์„ฑ๊ณต', + }, + WARNING: { + CREATE_FAIL: '๊ธ€ ๋“ฑ๋ก ์‹คํŒจ', + UPDATE_FAIL: '๊ธ€ ์ˆ˜์ • ์‹คํŒจ', + DELETE_FAIL: '๊ธ€ ์‚ญ์ œ ์‹คํŒจ', + }, + INFO: { + LOADING: '๋กœ๋”ฉ ์ค‘...', + }, + ERROR: { + NETWORK: '๋„คํŠธ์›Œํฌ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค', + SERVER: '์„œ๋ฒ„ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค', + }, +}; diff --git a/services/ahhachul.com/src/contexts/index.ts b/services/ahhachul.com/src/contexts/index.ts index 1b29bfa0d..8aedc8d38 100644 --- a/services/ahhachul.com/src/contexts/index.ts +++ b/services/ahhachul.com/src/contexts/index.ts @@ -1,3 +1,4 @@ export * from './auth'; export * from './native-bridge'; +export { default as Provider } from './provider'; export { default as QueryClientProvider } from './tanstack-query'; diff --git a/services/ahhachul.com/src/contexts/native-bridge.tsx b/services/ahhachul.com/src/contexts/native-bridge.tsx index aa455fa83..002adff02 100644 --- a/services/ahhachul.com/src/contexts/native-bridge.tsx +++ b/services/ahhachul.com/src/contexts/native-bridge.tsx @@ -30,11 +30,20 @@ export const NativeBridge: React.FC = ({ children }) => { }), ); }, - sendTextMessage: (number: string) => { + sendTextMessage: (number: string, message?: string) => { + const processedMessage = message + ? message + .replace(/\n/g, '%0A') + .replace(/[&]/g, '%26') + .replace(/[+]/g, '%2B') + .replace(/\s/g, '%20') + : ''; + window.ReactNativeWebView?.postMessage( JSON.stringify({ name: 'sendTextMessage', number, + message: processedMessage, }), ); }, @@ -54,6 +63,7 @@ export const NativeBridge: React.FC = ({ children }) => { ); }, share: (link: string) => { + console.log('link:', link); window.ReactNativeWebView?.postMessage( JSON.stringify({ name: 'share', diff --git a/services/ahhachul.com/src/contexts/provider.tsx b/services/ahhachul.com/src/contexts/provider.tsx new file mode 100644 index 000000000..9d1852699 --- /dev/null +++ b/services/ahhachul.com/src/contexts/provider.tsx @@ -0,0 +1,23 @@ +import { PropsWithChildren } from 'react'; + +import { ThemeProvider, Global } from '@emotion/react'; + +import { QueryClientProvider, AuthProvider, NativeBridge } from '@/contexts'; +import { globalStyles, theme } from '@/styles'; + +function AppProvider({ children }: PropsWithChildren) { + return ( + <> + + + + + {children} + + + + + ); +} + +export default AppProvider; diff --git a/services/ahhachul.com/src/contexts/tanstack-query.tsx b/services/ahhachul.com/src/contexts/tanstack-query.tsx index e31744a30..8020fba94 100644 --- a/services/ahhachul.com/src/contexts/tanstack-query.tsx +++ b/services/ahhachul.com/src/contexts/tanstack-query.tsx @@ -2,7 +2,7 @@ import React, { type PropsWithChildren } from 'react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -const queryClient = new QueryClient({ +export const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false, diff --git a/services/ahhachul.com/src/hooks/domain/complaint/index.ts b/services/ahhachul.com/src/hooks/domain/complaint/index.ts new file mode 100644 index 000000000..794892414 --- /dev/null +++ b/services/ahhachul.com/src/hooks/domain/complaint/index.ts @@ -0,0 +1,2 @@ +export { default as useComplaintForm } from './useComplaintForm'; +export { default as useComplaintFilters } from './useComplintFilterStore'; diff --git a/services/ahhachul.com/src/hooks/domain/complaint/useComplaintForm.ts b/services/ahhachul.com/src/hooks/domain/complaint/useComplaintForm.ts new file mode 100644 index 000000000..8c6889e13 --- /dev/null +++ b/services/ahhachul.com/src/hooks/domain/complaint/useComplaintForm.ts @@ -0,0 +1,81 @@ +import { useCallback } from 'react'; +import { useForm } from 'react-hook-form'; + +import { objectKeys } from '@ahhachul/utils'; + +import { complaintsContentDetail } from '@/constants'; +import { useCreateComplaint } from '@/services/complaint'; +import { KeyOf } from '@/types'; +import type { ComplaintForm } from '@/types/complaint'; +import { validateLexicalContent } from '@/utils/lexical'; + +const useComplaintForm = (slug: KeyOf) => { + const { mutate: createComplaintArticle, isPending } = useCreateComplaint(); + + const methods = useForm({ + mode: 'onBlur', + defaultValues: { + title: '', + content: '', + images: [], + subwayLineId: 1, + complaintType: slug, + shortContentType: objectKeys(complaintsContentDetail[slug].selectList)[0], + }, + }); + + const images = methods.watch('images'); + + const validateContent = useCallback( + (content: string) => validateLexicalContent(content, methods.setError), + [methods.setError], + ); + + const handleImageUpload = useCallback( + (files: File[]) => { + const fileBlob = files[0]; + if (!fileBlob) return; + + const newImages = [...images, ...files].slice(0, 5); + + methods.setValue('images', newImages, { shouldDirty: true }); + }, + [methods.setValue, images], + ); + + const handleImageDelete = useCallback( + (index: number) => { + const targetImage = images[index]; + if (!targetImage) return; + + methods.setValue( + 'images', + images.filter((_, i) => i !== index), + { shouldDirty: true }, + ); + }, + [images, methods.setValue], + ); + + const onSubmit = useCallback( + (data: ComplaintForm) => { + if (!validateContent(data.content)) return; + createComplaintArticle(data); + }, + [createComplaintArticle, validateContent], + ); + + const onError = useCallback(() => { + validateContent(methods.getValues('content')); + }, [methods.getValues, validateContent]); + + return { + methods, + isPending, + handleImageUpload, + handleImageDelete, + submit: methods.handleSubmit(onSubmit, onError), + }; +}; + +export default useComplaintForm; diff --git a/services/ahhachul.com/src/hooks/domain/complaint/useComplintFilterStore.ts b/services/ahhachul.com/src/hooks/domain/complaint/useComplintFilterStore.ts new file mode 100644 index 000000000..8b658f281 --- /dev/null +++ b/services/ahhachul.com/src/hooks/domain/complaint/useComplintFilterStore.ts @@ -0,0 +1,37 @@ +import { defaultComplaintFilterValues } from '@/constants'; +import { APP_UNIQUE_FILTER_ID_LIST } from '@/constants/filter'; +import { useActivity } from '@/stackflow'; +import { filterStore } from '@/stores'; +import type { IFilterState } from '@/stores/filter'; +import type { ComplaintFilters } from '@/types/complaint'; + +const useComplintFilters = () => { + const { + params: { keyword = '' }, + } = useActivity(); + + const { filters, loaded, activatedCount, handleSelect, handleReset } = + filterStore( + defaultComplaintFilterValues, + APP_UNIQUE_FILTER_ID_LIST.ComplaintPage, + )(); + + const boundaryKeys = [...Object.values(filters), keyword]; + + const getFilterProps = (): Omit, 'loaded'> => ({ + filters, + activatedCount, + handleSelect, + handleReset, + }); + + return { + loaded, + filters, + keyword, + boundaryKeys, + getFilterProps, + }; +}; + +export default useComplintFilters; diff --git a/services/ahhachul.com/src/hooks/domain/home/useInitialLoader.ts b/services/ahhachul.com/src/hooks/domain/home/useInitialLoader.ts new file mode 100644 index 000000000..6f9df296f --- /dev/null +++ b/services/ahhachul.com/src/hooks/domain/home/useInitialLoader.ts @@ -0,0 +1,15 @@ +import { useEffect } from 'react'; + +export const useInitialLoader = () => { + useEffect(() => { + const loader = document.getElementById('initial-loader'); + if (loader) { + loader.style.opacity = '0'; + loader.style.transition = 'opacity 0.5s ease'; + + setTimeout(() => { + loader.style.display = 'none'; + }, 500); + } + }, []); +}; diff --git a/services/ahhachul.com/src/hooks/domain/index.ts b/services/ahhachul.com/src/hooks/domain/index.ts index e13cab9cf..20d8a4a63 100644 --- a/services/ahhachul.com/src/hooks/domain/index.ts +++ b/services/ahhachul.com/src/hooks/domain/index.ts @@ -1,2 +1,4 @@ export * from './my'; export * from './lostFound'; +export * from './community'; +export * from './complaint'; diff --git a/services/ahhachul.com/src/hooks/domain/lostFound/useUpdateLostFound.ts b/services/ahhachul.com/src/hooks/domain/lostFound/useUpdateLostFound.ts new file mode 100644 index 000000000..95bccba05 --- /dev/null +++ b/services/ahhachul.com/src/hooks/domain/lostFound/useUpdateLostFound.ts @@ -0,0 +1,13 @@ +import { useMutation } from '@tanstack/react-query'; + +import { updateLostFoundStatus } from '@/apis/request'; +import { LostStatus } from '@/types'; + +const useUpdateLostFound = () => { + return useMutation({ + mutationFn: ({ articleId, status }: { articleId: number; status: LostStatus }) => + updateLostFoundStatus(articleId, status), + }); +}; + +export default useUpdateLostFound; diff --git a/services/ahhachul.com/src/hooks/domain/my/useUser.ts b/services/ahhachul.com/src/hooks/domain/my/useUser.ts index de402e1f5..b7ea72624 100644 --- a/services/ahhachul.com/src/hooks/domain/my/useUser.ts +++ b/services/ahhachul.com/src/hooks/domain/my/useUser.ts @@ -5,7 +5,7 @@ import { ApiResponse, UserProfileResponseDto } from '@/types'; const useUser = () => { const queryClient = useQueryClient(); - const data = queryClient.getQueryData>(userKeys.infos()); + const data = queryClient.getQueryData>(userKeys.info()); return { user: data?.result ?? null }; }; diff --git a/services/ahhachul.com/src/hooks/useCheckNickname.ts b/services/ahhachul.com/src/hooks/useCheckNickname.ts new file mode 100644 index 000000000..1266e0e37 --- /dev/null +++ b/services/ahhachul.com/src/hooks/useCheckNickname.ts @@ -0,0 +1,76 @@ +import { useEffect, useMemo, useRef, useState } from 'react'; + +import { useMutation } from '@tanstack/react-query'; +import { AxiosError } from 'axios'; +import { Subject, catchError, debounceTime, filter, from, map, mergeMap, of } from 'rxjs'; + +import axiosInstance from '@/apis/fetcher'; + +const MIN_LEN = 2; +const MAX_LEN = 10; + +interface Props { + nickname: string; + originNickname?: string; +} + +interface APICheckNicknameParams { + nickname: string; +} + +const checkNickname = (body: APICheckNicknameParams) => + axiosInstance.post(`/members/check-nickname`, body); +export const useCheckNickName = () => useMutation({ mutationFn: checkNickname }); + +export const useCheckNickname = ({ nickname, originNickname = '' }: Props) => { + const subject = useRef(new Subject()); + const { mutateAsync, status } = useCheckNickName(); + + const [errorMessage, setErrorMessage] = useState(''); + const disabled = useMemo(() => { + if (errorMessage !== '') return true; + if (status === 'pending') return true; + if (nickname.length < MIN_LEN || nickname.length > MAX_LEN) return true; + + return false; + }, [nickname, errorMessage, status]); + + useEffect(() => { + subject.current + .pipe( + debounceTime(500), + filter(v => v !== originNickname), + map(v => { + if (v.length > MAX_LEN) { + setErrorMessage('ํ•œ๊ธ€,์˜๋ฌธ 10์ž ์ดํ•˜๋กœ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”'); + return ''; + } else if (v.length === 1) { + setErrorMessage('์ตœ์†Œ 2์ž ์ด์ƒ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”'); + return ''; + } else { + setErrorMessage(''); + return v; + } + }), + filter(v => v !== ''), + mergeMap(v => from(mutateAsync({ nickname: v })).pipe(catchError(e => of(e)))), + map(d => { + if (d instanceof AxiosError) { + return '์ง€์›ํ•˜์ง€ ์•Š๋Š” ํ˜•์‹์ž…๋‹ˆ๋‹ค'; + } + return d.data.payload ? '์ค‘๋ณต์ธ ๋‹‰๋„ค์ž„์ด๋ผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.' : ''; + }), + ) + .subscribe((v: string) => { + setErrorMessage(v); + }); + + return () => subject.current?.unsubscribe(); + }, []); + + useEffect(() => { + subject.current.next(nickname); + }, [nickname]); + + return { errorMessage, disabled }; +}; diff --git a/services/ahhachul.com/src/hooks/useDisableScrolll.test.ts b/services/ahhachul.com/src/hooks/useDisableScrolll.test.ts index d2bb3c239..d615ddf2b 100644 --- a/services/ahhachul.com/src/hooks/useDisableScrolll.test.ts +++ b/services/ahhachul.com/src/hooks/useDisableScrolll.test.ts @@ -1,4 +1,4 @@ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { describe, expect, test, beforeEach } from 'vitest'; import useDisableScroll from './useDisableScroll'; @@ -17,12 +17,12 @@ describe('useDisableScroll', () => { }); test('๋งˆ์šดํŠธ๋˜๋ฉด overflow-y๊ฐ€ hidden์œผ๋กœ ์„ค์ •๋˜์–ด์•ผ ํ•œ๋‹ค', () => { - renderHook(useDisableScroll); + renderHook(() => useDisableScroll()); expect(document.body.style.overflowY).toBe('hidden'); }); test('์–ธ๋งˆ์šดํŠธ๋˜๋ฉด overflow-y๊ฐ€ scroll๋กœ ์ดˆ๊ธฐํ™”๋˜์–ด์•ผ ํ•œ๋‹ค', () => { - const { unmount } = renderHook(useDisableScroll); + const { unmount } = renderHook(() => useDisableScroll()); expect(document.body.style.overflowY).toBe('hidden'); unmount(); diff --git a/services/ahhachul.com/src/hooks/useOnClickOutside.ts b/services/ahhachul.com/src/hooks/useOnClickOutside.ts new file mode 100644 index 000000000..5bd3b91af --- /dev/null +++ b/services/ahhachul.com/src/hooks/useOnClickOutside.ts @@ -0,0 +1,25 @@ +import { useEffect, RefObject, ForwardedRef } from 'react'; + +const useOnClickOutside = ( + ref: RefObject | ForwardedRef, + handler: () => void, + exceptEl?: HTMLElement | null, +) => { + useEffect(() => { + const listener = (e: MouseEvent) => { + if (typeof ref === 'function' || !ref) return; + const el = ref?.current; + const isIncludeEl = !el || el.contains(e?.target as HTMLElement); + const isIncludeExceptEl = exceptEl && exceptEl?.contains(e?.target as HTMLElement); + + if (isIncludeEl || isIncludeExceptEl) return; + handler(); + }; + window.addEventListener('mousedown', listener); + return () => { + window.removeEventListener('mousedown', listener); + }; + }, [handler]); +}; + +export default useOnClickOutside; diff --git a/services/ahhachul.com/src/hooks/usePreloader.ts b/services/ahhachul.com/src/hooks/usePreloader.ts new file mode 100644 index 000000000..5a3164333 --- /dev/null +++ b/services/ahhachul.com/src/hooks/usePreloader.ts @@ -0,0 +1,5 @@ +import { createPreloader } from '@stackflow/plugin-preload'; + +import type { TypeActivities } from '@/stackflow'; + +export const { usePreloader } = createPreloader(); diff --git a/services/ahhachul.com/src/hooks/useToast.ts b/services/ahhachul.com/src/hooks/useToast.ts new file mode 100644 index 000000000..4c96101d4 --- /dev/null +++ b/services/ahhachul.com/src/hooks/useToast.ts @@ -0,0 +1,18 @@ +import { useToastStore, type ToastType } from '@/stores/toast'; + +export const useToast = () => { + const { addToast, removeToast } = useToastStore(); + + return { + addToast: (message: string, type: ToastType = 'info', duration?: number) => { + addToast(message, type, duration); + }, + removeToast, + toast: { + success: (message: string, duration?: number) => addToast(message, 'success', duration), + warning: (message: string, duration?: number) => addToast(message, 'warning', duration), + info: (message: string, duration?: number) => addToast(message, 'info', duration), + error: (message: string, duration?: number) => addToast(message, 'error', duration), + }, + }; +}; diff --git a/services/ahhachul.com/src/main.tsx b/services/ahhachul.com/src/main.tsx index f12e6409a..af10c6048 100644 --- a/services/ahhachul.com/src/main.tsx +++ b/services/ahhachul.com/src/main.tsx @@ -1,11 +1,55 @@ -import { Suspense } from 'react'; -import { createRoot } from 'react-dom/client'; +import { prefetchUserProfile, prefetchUserFavoriteStations } from './apis/request'; +import { prefetchSubwayLines } from './apis/request/subway'; +import { queryClient } from './contexts/tanstack-query'; +import { subwayKeys } from './services/subway'; +import { userKeys } from './services/user'; +import { useUserStationStore } from './stores/subway'; +import { getAccessTokenInLocalStorage } from './utils/localStorage'; -import App from './App'; -import { UiComponent } from './components'; +async function init() { + // ์ง€ํ•˜์ฒ  ์—ญ & ํ˜ธ์„  ์ •๋ณด prefetch + await queryClient.prefetchQuery({ + queryKey: subwayKeys.subwayLine(), + queryFn: prefetchSubwayLines, + staleTime: Infinity, + gcTime: Infinity, + }); -createRoot(document.getElementById('root')!).render( - }> - - , -); + const accessToken = getAccessTokenInLocalStorage(); + + if (accessToken) { + // ์œ ์ € ์ •๋ณด prefetch + try { + await queryClient.prefetchQuery({ + queryKey: userKeys.info(), + queryFn: prefetchUserProfile, + retry: false, + }); + } catch (error) { + console.log('Failed to prefetch user profile, continuing...'); + } + + // ์œ ์ € ์ฆ๊ฒจ์ฐพ๋Š” ์—ญ ์ •๋ณด prefetch + try { + const userStations = await queryClient.fetchQuery({ + queryKey: userKeys.stations(), + queryFn: prefetchUserFavoriteStations, + retry: false, + }); + + if (userStations.result.stationInfoList.length > 0) { + useUserStationStore.setState({ + userStations: userStations.result.stationInfoList, + }); + } + } catch (error) { + console.log('Failed to prefetch user stations, continuing...'); + } + } + + const { render } = await import('./render'); + + render(); +} + +init(); diff --git a/services/ahhachul.com/src/mocks/handlers/example.ts b/services/ahhachul.com/src/mocks/handlers/example.ts index 0bbeab20e..469bdd9c2 100644 --- a/services/ahhachul.com/src/mocks/handlers/example.ts +++ b/services/ahhachul.com/src/mocks/handlers/example.ts @@ -6,7 +6,7 @@ export const handlers = [ http.get('https://example.com/user', () => { // ...and respond to them using this JSON response. return HttpResponse.json({ - id: 'c7b3d8e0-5e0b-4b0f-8b3a-3b9f4b3d3b3d', + id: 'c7b3d8e0-5e0b-4b0f-8b3a-qwer1234xx', firstName: 'John', lastName: 'Maverick', }); diff --git a/services/ahhachul.com/src/pages/auth/callback.tsx b/services/ahhachul.com/src/pages/auth/callback.tsx index 29fc76234..5cc7b86bc 100644 --- a/services/ahhachul.com/src/pages/auth/callback.tsx +++ b/services/ahhachul.com/src/pages/auth/callback.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react/prop-types */ import { useEffect } from 'react'; import type { ActivityComponentType } from '@stackflow/react'; @@ -8,6 +7,7 @@ import { LayoutComponent, UiComponent } from '@/components'; import { useAuth } from '@/contexts'; import { useFlow } from '@/stackflow'; import { useTempAuth } from '@/stores'; +import { useUserStationStore } from '@/stores/subway'; import type { SocialSignInType } from '@/types'; interface SignInCallbackPageProps { @@ -17,6 +17,11 @@ interface SignInCallbackPageProps { const SignInCallbackPage: ActivityComponentType = ({ params: { type, code }, +}: { + params: { + type: string; + code: string; + }; }) => { const { replace } = useFlow(); const { authService } = useAuth(); @@ -44,7 +49,19 @@ const SignInCallbackPage: ActivityComponentType = ({ } authService.signIn({ accessToken, refreshToken }); - replace('HomePage', {}, { animate: false }); + + try { + const userStations = await api.fetchUserFavoriteStations(); + if (userStations.result.stationInfoList.length > 0) { + useUserStationStore.setState({ + userStations: userStations.result.stationInfoList, + }); + } + } catch (error) { + console.error(error); + } finally { + replace('HomePage', {}, { animate: false }); + } } catch (error) { console.error(error); replace('SignInPage', {}); diff --git a/services/ahhachul.com/src/pages/auth/set-nickname.tsx b/services/ahhachul.com/src/pages/auth/set-nickname.tsx index 31cf10a52..57b9de209 100644 --- a/services/ahhachul.com/src/pages/auth/set-nickname.tsx +++ b/services/ahhachul.com/src/pages/auth/set-nickname.tsx @@ -1,7 +1,97 @@ +import { useCallback, useState } from 'react'; + +import styled from '@emotion/styled'; +import { useMutation } from '@tanstack/react-query'; +import { type Variants as MotionVariantsType } from 'motion/react'; +import { motion } from 'motion/react'; + +import { updateUser } from '@/apis/request'; import { LayoutComponent } from '@/components'; +import { NicknameSetup } from '@/components/domain/auth/nickname/NicknameSetup'; +import { useAuth } from '@/contexts'; +import { useToast } from '@/hooks/useToast'; +import { useFlow } from '@/stackflow'; +import { useTempAuth } from '@/stores'; +import { mixins } from '@/styles'; + +const defaultEasing = [0.6, -0.05, 0.01, 0.99]; + +const animateVariants = (duration = 0.3): MotionVariantsType => ({ + initial: { + opacity: 0, + transition: { duration, ease: defaultEasing }, + willChange: 'opacity', + }, + animate: { + opacity: 1, + transition: { duration, ease: defaultEasing }, + willChange: 'opacity', + }, + exit: { + opacity: 0, + transition: { duration, ease: defaultEasing }, + willChange: 'opacity', + }, +}); const SetNickNamePage = () => { - return SetNickNamePage; + const { addToast } = useToast(); + + const { replace } = useFlow(); + const [nickname, setNickname] = useState(''); + const handleChange = useCallback( + (e: React.ChangeEvent) => setNickname(e.target.value), + [], + ); + + const { authService } = useAuth(); + const { tempTokens, clearTempTokens } = useTempAuth(); + const { mutate: updateUserAndTryLoginProcessDone } = useMutation({ + mutationFn: updateUser, + onSuccess: () => { + if (!tempTokens) return; + + const { accessToken, refreshToken } = tempTokens; + authService.signIn({ accessToken, refreshToken }); + clearTempTokens(); + addToast('๋กœ๊ทธ์ธ์„ ์„ฑ๊ณตํ–ˆ์–ด์š”', 'success'); + replace('HomePage', {}, { animate: false }); + }, + onError: error => { + console.log('API Error on updateUser:', error); + window.alert('๋กœ๊ทธ์ธ ์ •๋ณด๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š”๋ฐ ์‹คํŒจํ–ˆ์–ด์š”.'); + }, + }); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (!tempTokens?.accessToken) { + console.log('null accessToken'); + return; + } + updateUserAndTryLoginProcessDone({ nickname, auth: tempTokens }); + }; + + return ( + + + + + + ); }; +const Content = styled(motion.section)` + position: relative; + width: 100%; + height: 100%; + overflow: hidden; + background-color: ${({ theme }) => theme.colors.white}; +`; + export default SetNickNamePage; diff --git a/services/ahhachul.com/src/pages/comment/[commentId]/edit.tsx b/services/ahhachul.com/src/pages/comment/[commentId]/edit.tsx index f24bdd66f..e42a763fa 100644 --- a/services/ahhachul.com/src/pages/comment/[commentId]/edit.tsx +++ b/services/ahhachul.com/src/pages/comment/[commentId]/edit.tsx @@ -1,5 +1,166 @@ -const EditCommentPage = () => { - return
    edit
    ; +import { useMemo } from 'react'; + +import { css } from '@emotion/react'; +import styled from '@emotion/styled'; +import type { ActivityComponentType } from '@stackflow/react'; +import { useQueryClient } from '@tanstack/react-query'; + +import { formatDateTime, sleep } from '@ahhachul/utils'; + +import { LayoutComponent, UiComponent } from '@/components'; +import { CommentInput } from '@/components/common'; +import Comment from '@/components/common/comment/commentListItem/CommentListItem.component'; +import { useUpdateComment } from '@/services/comment'; +import { useFlow } from '@/stackflow'; +import { useTempComment } from '@/stores/comment'; +import type { WithPostId } from '@/types'; + +const EditCommentPage: ActivityComponentType< + { commentId: number; queryKey: readonly unknown[] } & WithPostId +> = ({ + params: { commentId, queryKey }, +}: { + params: { commentId: number; queryKey: readonly unknown[] } & WithPostId; +}) => { + const { tempComment } = useTempComment(); + + const targetCommentMap = useMemo( + () => + tempComment?.find(item => { + return ( + item.parentComment.id === commentId || + item.childComments.some(child => child.id === commentId) + ); + }), + [tempComment, commentId], + ); + + const parentComment = targetCommentMap?.parentComment; + const targetComment = + parentComment?.id === commentId + ? parentComment + : targetCommentMap?.childComments.find(childComment => childComment.id === commentId); + + const { pop } = useFlow(); + const { mutate } = useUpdateComment(); + const queryClient = useQueryClient(); + + const editComment = ({ comment }: { isPrivate: boolean; comment: string }) => { + mutate( + { + commentId, + content: comment, + }, + { + onSuccess: async () => { + queryClient.invalidateQueries({ + queryKey: queryKey, + }); + + await sleep(250); + pop(); + }, + }, + ); + }; + + if (!targetComment) return ; + + return ( + + + + {targetComment?.writer} + + + + {formatDateTime(targetComment?.createdAt || '', { format: 'short' })} + + + + + + + + + + + + {targetCommentMap?.childComments?.map(childComment => ( + + ))} + + + + ); }; +const ArticleWrapper = styled.article` + border-bottom: 1px solid ${({ theme }) => theme.colors.gray[20]}; +`; + +const ContentWrapper = styled.div` + padding: 20px 20px 24px; +`; + +const TitleWrapper = styled.div` + ${({ theme }) => css` + ${theme.fonts.titleLarge}; + color: ${theme.colors.gray[90]}; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + `} +`; + +const MetaInfoWrapper = styled.div` + display: flex; + width: 100%; + align-items: center; + justify-content: space-between; + padding-bottom: 16px; + border-bottom: 1px solid ${({ theme }) => theme.colors.gray[20]}; +`; + +const AuthorDateWrapper = styled.div` + ${({ theme }) => css` + ${theme.fonts.bodyMedium}; + display: flex; + align-items: center; + gap: 4px; + `} +`; + +const DateText = styled.span` + color: ${({ theme }) => theme.colors.gray[70]}; +`; + +const ContentContainer = styled.div` + padding: 0 20px; +`; + +const LexicalContent = styled.div` + padding: 0 0 24px; + + & > div { + padding: 0; + & > div > div { + padding: 0; + border: none; + } + } +`; + +const Padding = styled.div` + width: 100%; + height: 234px; +`; + export default EditCommentPage; diff --git a/services/ahhachul.com/src/pages/comment/[commentId]/reply.tsx b/services/ahhachul.com/src/pages/comment/[commentId]/reply.tsx index 04bf7a211..0c0cc4b33 100644 --- a/services/ahhachul.com/src/pages/comment/[commentId]/reply.tsx +++ b/services/ahhachul.com/src/pages/comment/[commentId]/reply.tsx @@ -1,5 +1,177 @@ -const NewCommentReplyPage = () => { - return
    edit
    ; +import { useMemo } from 'react'; + +import { css } from '@emotion/react'; +import styled from '@emotion/styled'; +import type { ActivityComponentType } from '@stackflow/react'; +import { useQueryClient } from '@tanstack/react-query'; + +import { formatDateTime, sleep } from '@ahhachul/utils'; + +import { LayoutComponent, UiComponent } from '@/components'; +import { CommentInput } from '@/components/common'; +import Comment from '@/components/common/comment/commentListItem/CommentListItem.component'; +import { usePostComment } from '@/services/comment'; +import { useTempComment } from '@/stores/comment'; +import type { WithPostId } from '@/types'; + +const NewCommentReplyPage: ActivityComponentType< + { commentId: number; queryKey: readonly unknown[]; servicePath: string } & WithPostId +> = ({ + params: { id, commentId, queryKey, servicePath }, +}: { + params: { commentId: number; queryKey: readonly unknown[]; servicePath: string } & WithPostId; +}) => { + const { tempComment } = useTempComment(); + + const targetCommentMap = useMemo( + () => + tempComment?.find(item => { + return ( + item.parentComment.id === commentId || + item.childComments.some(child => child.id === commentId) + ); + }), + [tempComment, commentId], + ); + + const parentComment = targetCommentMap?.parentComment; + const targetComment = + parentComment?.id === commentId + ? parentComment + : targetCommentMap?.childComments.find(childComment => childComment.id === commentId); + + const { mutate } = usePostComment(); + const queryClient = useQueryClient(); + + const submitComment = ({ comment }: { isPrivate: boolean; comment: string }) => { + mutate( + { + postId: id, + content: comment, + upperCommentId: targetComment?.id || null, + servicePath, + }, + { + onSuccess: async res => { + queryClient.invalidateQueries({ + queryKey: queryKey, + }); + + await sleep(250); + + const comment = document.querySelector(`[data-comment-id="${res.result.id}"]`); + if (comment) { + comment.scrollIntoView({ + block: 'start', + behavior: 'smooth', + }); + } + }, + }, + ); + }; + + if (!targetComment) return ; + + return ( + + + + + {targetComment?.writer} + {targetComment?.isPrivate && ( + (๋น„๊ณต๊ฐœ) + )} + + + + + {formatDateTime(targetComment?.createdAt || '', { format: 'short' })} + + + + + + + + + + + + {targetCommentMap?.childComments?.map(childComment => ( + + ))} + + + + ); }; +const ArticleWrapper = styled.article` + border-bottom: 1px solid ${({ theme }) => theme.colors.gray[20]}; +`; + +const ContentWrapper = styled.div` + padding: 20px 20px 24px; +`; + +const TitleWrapper = styled.div` + ${({ theme }) => css` + ${theme.fonts.titleLarge}; + color: ${theme.colors.gray[90]}; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + `} +`; + +const MetaInfoWrapper = styled.div` + display: flex; + width: 100%; + align-items: center; + justify-content: space-between; + padding-bottom: 16px; + border-bottom: 1px solid ${({ theme }) => theme.colors.gray[20]}; +`; + +const AuthorDateWrapper = styled.div` + ${({ theme }) => css` + ${theme.fonts.bodyMedium}; + display: flex; + align-items: center; + gap: 4px; + `} +`; + +const DateText = styled.span` + color: ${({ theme }) => theme.colors.gray[70]}; +`; + +const ContentContainer = styled.div` + padding: 0 20px; +`; + +const LexicalContent = styled.div` + padding: 0 0 24px; + + & > div { + padding: 0; + & > div > div { + padding: 0; + border: none; + } + } +`; + +const Padding = styled.div` + width: 100%; + height: 234px; +`; + export default NewCommentReplyPage; diff --git a/services/ahhachul.com/src/pages/community/[id]/edit.tsx b/services/ahhachul.com/src/pages/community/[id]/edit.tsx index 6ba32fb36..d9f047a04 100644 --- a/services/ahhachul.com/src/pages/community/[id]/edit.tsx +++ b/services/ahhachul.com/src/pages/community/[id]/edit.tsx @@ -1,10 +1,13 @@ -/* eslint-disable react/prop-types */ import type { ActivityComponentType } from '@stackflow/react'; import { LayoutComponent, CommunityComponent, UiComponent } from '@/components'; import type { WithPostId } from '@/types'; -const EditCommunityPage: ActivityComponentType = ({ params: { id } }) => { +const EditCommunityPage: ActivityComponentType = ({ + params: { id }, +}: { + params: WithPostId; +}) => { return ( = ({ params: { id } }) => { +const CommunityDetailPage: ActivityComponentType = ({ + params: { id }, +}: { + params: WithPostId; +}) => { return ( { const { methods, isPending, handleImageUpload, handleImageDelete, submit } = useCommunityForm(); return ( - + { - + ); }; diff --git a/services/ahhachul.com/src/pages/community/page.tsx b/services/ahhachul.com/src/pages/community/page.tsx index c5eaa535e..a707058a0 100644 --- a/services/ahhachul.com/src/pages/community/page.tsx +++ b/services/ahhachul.com/src/pages/community/page.tsx @@ -1,8 +1,15 @@ -import { useReducer } from 'react'; +import React, { useReducer } from 'react'; import { CommunityComponent, HeaderComponent, LayoutComponent, UiComponent } from '@/components'; import { useCommunityFilters } from '@/hooks/domain/community'; +const SearchedListSkeleton = React.lazy( + () => import('@/components/domain/community/searchResults/skeleton/SearchedList.skeleton'), +); +const SearchedList = React.lazy( + () => import('@/components/domain/community/searchResults/searchedList/SearchedList.component'), +); + const CommunityPage = () => { const [isScale, toggleScale] = useReducer(scale => !scale, false); @@ -29,9 +36,9 @@ const CommunityPage = () => { } - suspenseFallback={} + suspenseFallback={} > - + diff --git a/services/ahhachul.com/src/pages/complaint/[id]/edit.tsx b/services/ahhachul.com/src/pages/complaint/[id]/edit.tsx index 45328dd76..c4da43dbf 100644 --- a/services/ahhachul.com/src/pages/complaint/[id]/edit.tsx +++ b/services/ahhachul.com/src/pages/complaint/[id]/edit.tsx @@ -1,7 +1,12 @@ import { LayoutComponent } from '@/components'; +import { MaintainContent } from '@/components/common'; const EditComplaintPage = () => { - return EditComplaintPage; + return ( + + + + ); }; export default EditComplaintPage; diff --git a/services/ahhachul.com/src/pages/complaint/[id]/page.tsx b/services/ahhachul.com/src/pages/complaint/[id]/page.tsx index ac2b4120a..8e9038a09 100644 --- a/services/ahhachul.com/src/pages/complaint/[id]/page.tsx +++ b/services/ahhachul.com/src/pages/complaint/[id]/page.tsx @@ -1,7 +1,24 @@ -import { LayoutComponent } from '@/components'; +import { type ActivityComponentType } from '@stackflow/react'; -const ComplaintDetailPage = () => { - return ComplaintDetailPage; +import { ComplaintComponent, LayoutComponent, UiComponent } from '@/components'; +import type { WithPostId } from '@/types'; + +const ComplaintDetailPage: ActivityComponentType = ({ + params: { id }, +}: { + params: WithPostId; +}) => { + return ( + + } + errorFallback={props => } + > + + + + ); }; export default ComplaintDetailPage; diff --git a/services/ahhachul.com/src/pages/complaint/index.ts b/services/ahhachul.com/src/pages/complaint/index.ts index ad7926212..6a57fc7dd 100644 --- a/services/ahhachul.com/src/pages/complaint/index.ts +++ b/services/ahhachul.com/src/pages/complaint/index.ts @@ -1,4 +1,4 @@ export { default as ComplaintPage } from './page'; export { default as NewComplaintPage } from './new'; -export { default as ComplaintListPage } from './list'; +export { default as ComplaintPanelPage } from './panel'; export * from './[id]'; diff --git a/services/ahhachul.com/src/pages/complaint/list.tsx b/services/ahhachul.com/src/pages/complaint/list.tsx deleted file mode 100644 index b7e14c288..000000000 --- a/services/ahhachul.com/src/pages/complaint/list.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { LayoutComponent } from '@/components'; - -const ComplaintListPage = () => { - return ComplaintListPage; -}; - -export default ComplaintListPage; diff --git a/services/ahhachul.com/src/pages/complaint/new.tsx b/services/ahhachul.com/src/pages/complaint/new.tsx index 8c59390d4..059e6bc6d 100644 --- a/services/ahhachul.com/src/pages/complaint/new.tsx +++ b/services/ahhachul.com/src/pages/complaint/new.tsx @@ -1,7 +1,68 @@ -import { LayoutComponent } from '@/components'; +import { FormProvider } from 'react-hook-form'; -const NewComplaintPage = () => { - return NewComplaintPage; +import styled from '@emotion/styled'; +import type { ActivityComponentType } from '@stackflow/react'; + +import { LayoutComponent, FormComponent } from '@/components'; +import { complaintsContentDetail } from '@/constants'; +import useComplaintForm from '@/hooks/domain/complaint/useComplaintForm'; +import { useActivity } from '@/stackflow'; +import { mixins } from '@/styles'; +import { KeyOf } from '@/types'; + +interface ComplaintFormProps { + slug: KeyOf; +} + +const NewComplaintPage: ActivityComponentType = ({ + params: { slug }, +}: { + params: { + slug: KeyOf; + }; +}) => { + const information = complaintsContentDetail[slug]; + + const { isActive } = useActivity(); + + const { methods, isPending, handleImageUpload, handleImageDelete, submit } = + useComplaintForm(slug); + + return ( + + + + + + + + + + + + + ); +}; + +const S = { + FormContainer: styled.div` + ${mixins.fullWidth}; + ${mixins.flexColumn}; + ${mixins.pagePaddingTop}; + ${mixins.pagePaddingBottom}; + `, }; export default NewComplaintPage; diff --git a/services/ahhachul.com/src/pages/complaint/page.tsx b/services/ahhachul.com/src/pages/complaint/page.tsx index 0950ed0ae..1183e7a33 100644 --- a/services/ahhachul.com/src/pages/complaint/page.tsx +++ b/services/ahhachul.com/src/pages/complaint/page.tsx @@ -1,19 +1,56 @@ -import { ActivityComponentType } from '@stackflow/react'; +import React, { useReducer } from 'react'; -import { HeaderComponent, LayoutComponent } from '@/components'; +import type { ActivityComponentType } from '@stackflow/react'; + +import { HeaderComponent, LayoutComponent, UiComponent } from '@/components'; +import { ComplaintComponent } from '@/components/domain'; +import { useComplaintFilters } from '@/hooks/domain'; + +const SearchedListSkeleton = React.lazy( + () => import('@/components/domain/complaint/searchResults/skeleton/SearchedList.skeleton'), +); +const SearchedList = React.lazy( + () => import('@/components/domain/complaint/searchResults/searchedList/SearchedList.component'), +); const ComplaintPage: ActivityComponentType = () => { + const [isScale, toggleScale] = useReducer(scale => !scale, false); + + const { loaded, keyword, filters, boundaryKeys, getFilterProps } = useComplaintFilters(); + + if (!loaded) { + return ; + } + return ( - + } > - {/* */} - ComplaintPage - + } + suspenseFallback={} + > + + + + + ); }; diff --git a/services/ahhachul.com/src/pages/complaint/panel.tsx b/services/ahhachul.com/src/pages/complaint/panel.tsx new file mode 100644 index 000000000..c840b9e9c --- /dev/null +++ b/services/ahhachul.com/src/pages/complaint/panel.tsx @@ -0,0 +1,27 @@ +import { ActivityComponentType } from '@stackflow/react'; + +import { HeaderComponent, LayoutComponent, UiComponent } from '@/components'; +import { ComplaintPanel } from '@/components/domain/complaint'; + +const ComplaintListPage: ActivityComponentType = () => { + return ( + + + + + ); +}; + +export default ComplaintListPage; diff --git a/services/ahhachul.com/src/pages/hashtag/index.ts b/services/ahhachul.com/src/pages/hashtag/index.ts new file mode 100644 index 000000000..fbb8d1725 --- /dev/null +++ b/services/ahhachul.com/src/pages/hashtag/index.ts @@ -0,0 +1 @@ +export { default as HashtagPage } from './page'; diff --git a/services/ahhachul.com/src/pages/hashtag/page.tsx b/services/ahhachul.com/src/pages/hashtag/page.tsx new file mode 100644 index 000000000..1b3b16d2c --- /dev/null +++ b/services/ahhachul.com/src/pages/hashtag/page.tsx @@ -0,0 +1,209 @@ +import React, { useCallback, useEffect, useRef } from 'react'; + +import { css } from '@emotion/react'; +import styled from '@emotion/styled'; +import { useActivity } from '@stackflow/react'; +import { motion } from 'motion/react'; +import { debounceTime, Subject } from 'rxjs'; + +import { SearchIcon } from '@/assets/icons/system'; +import { HomeComponent, LayoutComponent, UiComponent } from '@/components'; +import { useFlow, useStepFlow } from '@/stackflow'; +import { SubwayLineFilterOptions } from '@/types'; + +const SearchedListSkeleton = React.lazy( + () => import('@/components/domain/community/searchResults/skeleton/SearchedList.skeleton'), +); +const SearchedList = React.lazy( + () => import('@/components/domain/community/searchResults/searchedList/SearchedList.component'), +); + +const HashtagPage = ({ params: { tag } }: any) => { + const { pop } = useFlow(); + const { isActive } = useActivity(); + + return ( + +
    pop()} + > + +
    + + + } + > + } + suspenseFallback={} + > + + +
    + ); +}; + +const HashtagSearchInput = ({ hashTag }: { hashTag?: string }) => { + const { stepPush } = useStepFlow('HashtagPage'); + + const updateKeyword = (value: string) => { + stepPush({ + tag: value.trim(), + }); + }; + + const subject = useRef(new Subject()); + const inputRef = useRef(null); + + const handleInputChange = useCallback((e: React.ChangeEvent) => { + const value = e.target.value; + subject.current.next(value); + }, []); + + const handleFormSubmit = useCallback((e: React.FormEvent) => { + e.preventDefault(); + const form = e.currentTarget; + const formData = new FormData(form); + const inputValue = formData.get('search') as string; + updateKeyword(inputValue); + inputRef.current?.blur(); + }, []); + + useEffect(() => { + const subscription = subject.current + .pipe(debounceTime(300)) + .subscribe(value => updateKeyword(value)); + + return () => subscription.unsubscribe(); + }, [updateKeyword]); + + return ( +
    + + + + + + ); +}; + +interface FilterGroupProps { + isScale: boolean; + isActive: boolean; +} + +const FilterGroup = styled.div` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 58px; + display: flex; + justify-content: center; + border-bottom: 1px solid ${({ theme }) => theme.colors.gray[20]}; + background-color: ${({ theme }) => theme.colors.white}; + padding-right: 24px; + z-index: 50; + display: ${({ isActive }) => (isActive ? 'flex' : 'none')}; +`; + +const Form = styled.form` + ${({ theme }) => css` + position: relative; + display: flex; + align-items: center; + gap: 14px; + width: 100%; + background-color: ${theme.colors.white}; + `} +`; + +const SearchIconWrapper = styled.button` + position: absolute; + left: 6px; + top: 48%; + transform: translateY(-50%); + width: 20px; + height: 20px; + + .search-icon { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + opacity: 0.7; + } +`; + +const StyledSearchInput = styled(motion.input)` + ${({ theme }) => css` + width: 100%; + max-width: 100%; + height: 36px; + border-radius: 9px; + padding: 0 12px 0 29px; + font-size: 15px; + color: ${theme.colors.gray[90]}; + background-color: ${theme.colors.gray[20]}; + caret-color: rgba(0, 255, 163, 0.5); + transition: all 0.3s ease; + border: 0; + + &::placeholder { + font-size: 15px; + color: ${theme.colors.gray[70]}; + } + + &:active:not(:focus) { + background-color: rgba(119, 119, 119, 0.8); + } + `} +`; + +export default HashtagPage; diff --git a/services/ahhachul.com/src/pages/home/page.tsx b/services/ahhachul.com/src/pages/home/page.tsx index 840fcb9af..d7d4ac803 100644 --- a/services/ahhachul.com/src/pages/home/page.tsx +++ b/services/ahhachul.com/src/pages/home/page.tsx @@ -1,22 +1,25 @@ import styled from '@emotion/styled'; import type { ActivityComponentType } from '@stackflow/react'; -import { HeaderComponent, HomeComponent, LayoutComponent } from '@/components'; -import { mixins } from '@/styles'; +import { HomeComponent, LayoutComponent } from '@/components'; +import { mixins, theme } from '@/styles'; const HomePage: ActivityComponentType = () => { return ( + + ); @@ -27,7 +30,6 @@ const S = { ${mixins.fullWidth}; ${mixins.flexColumn}; ${mixins.pagePaddingTop}; - ${mixins.pagePaddingBottom}; `, }; diff --git a/services/ahhachul.com/src/pages/index.ts b/services/ahhachul.com/src/pages/index.ts index 604182c24..967eb9afd 100644 --- a/services/ahhachul.com/src/pages/index.ts +++ b/services/ahhachul.com/src/pages/index.ts @@ -1,5 +1,8 @@ import { EditCommentPage, NewCommentReplyPage } from './comment'; +import { HashtagPage } from './hashtag'; +import { NewsDetailPage } from './news'; import { NotificationPage, NotificationSettingPage } from './notification'; +import { SubwayMapPage, SubwayTimelinePage } from './subway'; import { TalkPage, TalkDetailPage, TalkSettingPage } from './talk'; export const SharingPages = { @@ -10,6 +13,10 @@ export const SharingPages = { NotificationSettingPage, EditCommentPage, NewCommentReplyPage, + NewsDetailPage, + HashtagPage, + SubwayMapPage, + SubwayTimelinePage, } as const; export * as MyPages from './my'; diff --git a/services/ahhachul.com/src/pages/lostFound/[id]/edit.tsx b/services/ahhachul.com/src/pages/lostFound/[id]/edit.tsx index 3f6e66eaf..0fad1abcd 100644 --- a/services/ahhachul.com/src/pages/lostFound/[id]/edit.tsx +++ b/services/ahhachul.com/src/pages/lostFound/[id]/edit.tsx @@ -1,10 +1,13 @@ -/* eslint-disable react/prop-types */ import type { ActivityComponentType } from '@stackflow/react'; import { LayoutComponent, LostFoundComponent, UiComponent } from '@/components'; import type { WithPostId } from '@/types'; -const EditLostFoundPage: ActivityComponentType = ({ params: { id } }) => { +const EditLostFoundPage: ActivityComponentType = ({ + params: { id }, +}: { + params: WithPostId; +}) => { return ( = ({ params: { id } }) => { +const LostFoundDetailPage: ActivityComponentType = ({ + params: { id }, +}: { + params: WithPostId; +}) => { return ( { const { methods, isPending, handleImageUpload, handleImageDelete, submit } = useLostFoundForm(); return ( - + { - + ); }; diff --git a/services/ahhachul.com/src/pages/lostFound/page.tsx b/services/ahhachul.com/src/pages/lostFound/page.tsx index 8746e3e71..6ff78ba3d 100644 --- a/services/ahhachul.com/src/pages/lostFound/page.tsx +++ b/services/ahhachul.com/src/pages/lostFound/page.tsx @@ -1,4 +1,4 @@ -import { useReducer } from 'react'; +import React, { useReducer } from 'react'; import type { ActivityComponentType } from '@stackflow/react'; @@ -6,6 +6,13 @@ import { HeaderComponent, LayoutComponent, UiComponent } from '@/components'; import { LostFoundComponent } from '@/components/domain'; import { useLostFoundFilters } from '@/hooks/domain'; +const SearchedListSkeleton = React.lazy( + () => import('@/components/domain/lostFound/searchResults/skeleton/SearchedList.skeleton'), +); +const SearchedList = React.lazy( + () => import('@/components/domain/lostFound/searchResults/searchedList/SearchedList.component'), +); + const LostFoundPage: ActivityComponentType = () => { const [isScale, toggleScale] = useReducer(scale => !scale, false); @@ -32,9 +39,9 @@ const LostFoundPage: ActivityComponentType = () => { } - suspenseFallback={} + suspenseFallback={} > - + diff --git a/services/ahhachul.com/src/pages/my/account.tsx b/services/ahhachul.com/src/pages/my/account.tsx new file mode 100644 index 000000000..7684545ef --- /dev/null +++ b/services/ahhachul.com/src/pages/my/account.tsx @@ -0,0 +1,187 @@ +import { css } from '@emotion/react'; +import styled from '@emotion/styled'; +import type { ActivityComponentType } from '@stackflow/react'; + +import { ChevronIcon } from '@/assets/icons/system'; +import CameraImg from '@/assets/images/icon_camera.png'; +import { LayoutComponent } from '@/components'; +import { Avatar } from '@/components/common/avatar/Avatar.component'; +import { useAuth } from '@/contexts'; +import { useToast } from '@/hooks/useToast'; +import { useFetchUserProfile } from '@/services/user'; + +const MyAccountPage: ActivityComponentType = () => { + const { addToast } = useToast(); + const { isCheckingAuthState } = useAuth(); + const { data: userInfo, isLoading } = useFetchUserProfile(); + + const showToast = () => addToast('์ค€๋น„์ค‘์ธ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค.', 'info'); + + if (isLoading || isCheckingAuthState) return null; + + return ( + + + + + + + CameraImg + + + + + +
    +

    ์ด๋ฉ”์ผ

    +

    {userInfo?.result?.email ?? '-'}

    +
    +
    +

    ๋‹‰๋„ค์ž„

    +
    +

    {userInfo?.result?.nickname}

    + +
    +
    +
    +

    ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ

    + +
    + + + + ์•„ํ•˜์ฒ  ์•ฑ ๊ณ„์ • ํƒˆํ‡ด + + + +
    + + + + +
    +
    + ); +}; + +const Wrapper = styled.div` + width: 100%; + height: auto; + display: flex; + flex-direction: column; + justify-content: space-between; + padding: 16px 24px 0px 20px; +`; + +const FlexCenter = styled.div` + display: flex; + justify-content: center; + align-items: center; +`; + +const AvatarWrapper = styled.div` + position: relative; +`; + +const UploadIcon = styled.div` + background-color: transparent; + + & > img { + width: 30px; + height: 30px; + } + + position: absolute; + top: 50px; + left: 50px; +`; + +const Fields = styled.div` + display: flex; + flex-direction: column; + margin-top: 16px; + + > div { + display: flex; + align-items: center; + justify-content: space-between; + height: 56px; + + & > svg { + transform: rotate(270deg); + } + } + + div > div { + display: flex; + align-items: center; + + p { + margin-right: 8px; + } + + & > svg { + transform: rotate(270deg); + } + } + + p.main { + font-size: 16px; + font-weight: 500; + line-height: 24px; + color: #26282b; + } + + p.secondary { + font-size: 16px; + color: #70747d; + } +`; + +const LogoutButtonWrapper = styled.div` + display: flex; + padding: 0 20px; + position: absolute; + width: 100%; + left: 0; + bottom: 40px; +`; + +const RemoveAccountCard = styled.div` + height: 48px !important; + display: flex; + justify-content: flex-start !important; + align-items: center; + + & > svg { + transform: rotate(270deg); + + & > g > path { + fill: #838791; + } + } +`; + +const Divider = styled.span` + border-top: 1px solid #e1e6ed; + margin: 16px 0; +`; + +export default MyAccountPage; diff --git a/services/ahhachul.com/src/pages/my/index.ts b/services/ahhachul.com/src/pages/my/index.ts index 0ccb2f94d..700c40f8f 100644 --- a/services/ahhachul.com/src/pages/my/index.ts +++ b/services/ahhachul.com/src/pages/my/index.ts @@ -1,2 +1,3 @@ export { default as MyPage } from './page'; export { default as SettingPage } from './setting'; +export { default as MyAccountPage } from './account'; diff --git a/services/ahhachul.com/src/pages/my/page.tsx b/services/ahhachul.com/src/pages/my/page.tsx index 52c44401a..c45109a44 100644 --- a/services/ahhachul.com/src/pages/my/page.tsx +++ b/services/ahhachul.com/src/pages/my/page.tsx @@ -1,8 +1,17 @@ -import { ActivityComponentType } from '@stackflow/react'; +import styled from '@emotion/styled'; +import type { ActivityComponentType } from '@stackflow/react'; import { HeaderComponent, LayoutComponent } from '@/components'; +import MenuSections from '@/components/domain/my/MenuSections.component'; +import RequestCard from '@/components/domain/my/RequestCard.component'; +import UserProfile from '@/components/domain/my/UserProfile.component'; +import { useFetchSubwayLines } from '@/services/subway'; +import { useFetchUserProfile } from '@/services/user'; const MyPage: ActivityComponentType = () => { + useFetchSubwayLines(); + useFetchUserProfile(); + return ( { renderRight: HeaderComponent.HeaderActions, }} > - MyPage + + + + + + + ); }; +const S = { + Container: styled.div` + width: 100%; + height: 100%; + position: relative; + `, + Wrapper: styled.div` + display: flex; + flex-direction: column; + width: 100%; + height: auto; + padding: 0 20px; + `, +}; export default MyPage; diff --git a/services/ahhachul.com/src/pages/my/setting.tsx b/services/ahhachul.com/src/pages/my/setting.tsx index 3b30f3240..e02e594f5 100644 --- a/services/ahhachul.com/src/pages/my/setting.tsx +++ b/services/ahhachul.com/src/pages/my/setting.tsx @@ -1,7 +1,434 @@ +import type React from 'react'; +import { useEffect, useState, useTransition } from 'react'; + +import { css } from '@emotion/react'; +import styled from '@emotion/styled'; +import type { ActivityComponentType } from '@stackflow/react'; + +import { HomeMiniIcon, OfficeMiniIcon, SchoolMiniIcon, StarMiniIcon } from '@/assets/icons/setting'; +import { CloseIcon, SearchIcon } from '@/assets/icons/system'; import { LayoutComponent } from '@/components'; +import { subwayLineHexColors, subwayLineOptions } from '@/constants'; +import { useFetchSubwayLines } from '@/services/subway'; +import { useUserFavoriteStations } from '@/services/user'; +import { useFlow } from '@/stackflow'; +import { useUserStationStore } from '@/stores/subway'; +import type { Stations, SubwayLineType } from '@/types'; +import { applyHighlight } from '@/utils/text'; + +interface StationLabel { + stationName: string; + label: '์ง‘' | 'ํšŒ์‚ฌ' | 'ํ•™๊ต' | '์ฆ๊ฒจ์ฐพ๋Š” ์žฅ์†Œ'; +} + +const LABEL_OPTIONS = [ + { id: '์ง‘', icon: , text: '์ง‘' }, + { id: 'ํšŒ์‚ฌ', icon: , text: 'ํšŒ์‚ฌ' }, + { id: 'ํ•™๊ต', icon: , text: 'ํ•™๊ต' }, + { id: '์ฆ๊ฒจ์ฐพ๋Š” ์žฅ์†Œ', icon: , text: '์ฆ๊ฒจ์ฐพ๋Š” ์žฅ์†Œ' }, +] as const; + +const SettingPage: ActivityComponentType = () => { + const { pop } = useFlow(); + const { data: DEFAULT_STATIONS } = useFetchSubwayLines(); + const { userStations } = useUserStationStore(state => state); + const { mutate: updateUserFavoriteStations } = useUserFavoriteStations(); + + const [searchTerm, setSearchTerm] = useState(''); + const [selectedStation, setSelectedStation] = useState(null); + const [labeledStations, setLabeledStations] = useState([]); + const [isPending, startTransition] = useTransition(); + + const allStations = Object.keys(DEFAULT_STATIONS as Stations); + + const displayStations = searchTerm + ? allStations.filter(name => name.toLowerCase().includes(searchTerm.toLowerCase())) + : allStations; + + const renderLineNumbers = (stationName: string) => { + return ( + + {DEFAULT_STATIONS?.[stationName as keyof typeof DEFAULT_STATIONS].map((station, idx) => ( + + {subwayLineOptions[String(station.parentLineId) as SubwayLineType]?.slice(0, 1)} + + ))} + + ); + }; + + const handleStationSelect = (stationName: string) => { + setSelectedStation(selectedStation === stationName ? null : stationName); + }; + + const handleLabelSelect = (label: StationLabel['label']) => { + if (!selectedStation) return; + + const filteredStations = labeledStations.filter(s => s.stationName !== selectedStation); + + setLabeledStations([...filteredStations, { stationName: selectedStation, label }]); + }; + + const getDefaultLabel = () => { + const hasHome = labeledStations.some(s => s.label === '์ง‘'); + return hasHome ? 'ํšŒ์‚ฌ' : '์ง‘'; + }; + + const getStationLabel = (stationName: string) => { + return labeledStations.find(s => s.stationName === stationName)?.label; + }; + + const handleSearchChange = (e: React.ChangeEvent) => { + const value = e.target.value; + setSearchTerm(value); + + startTransition(() => { + setSearchTerm(value); + }); + }; + + useEffect(() => { + if (userStations.length > 0) { + setLabeledStations( + userStations.map(item => ({ + stationName: item.stationName, + label: item.label as '์ง‘' | 'ํšŒ์‚ฌ' | 'ํ•™๊ต' | '์ฆ๊ฒจ์ฐพ๋Š” ์žฅ์†Œ', + })), + ); + } + }, [userStations]); + + return ( + + + + ์ฆ๊ฒจ์ฐพ๋Š” ์—ญ์„ ์„ค์ •ํ•ด์ฃผ์„ธ์š” + + ์ตœ๋Œ€ 4๊ฐœ๊นŒ์ง€ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ์–ด์š” + + + + + + + + + {displayStations.map((name, idx) => ( + + handleStationSelect(name)}> + {renderLineNumbers(name)} + {applyHighlight(searchTerm, name)} + {getStationLabel(name) && ( + + {LABEL_OPTIONS.find(l => l.id === getStationLabel(name))?.icon} + + )} + + + {selectedStation === name && ( + + {LABEL_OPTIONS.map((option, idx) => { + const isDisabled = + option.id !== getStationLabel(name) && + labeledStations.some(s => s.label === option.id); + + return ( + { + if (getStationLabel(name) === option.id) { + setLabeledStations(labeledStations.filter(s => s.stationName !== name)); + } else { + handleLabelSelect(option.id); + } + }} + $isActive={getStationLabel(name) === option.id} + $isDefault={!getStationLabel(name) && option.id === getDefaultLabel()} + disabled={isDisabled} + > + {option.icon} {option.text} + + ); + })} + + )} + + ))} + {/* */} + + + {labeledStations.length > 0 && ( +
    + {labeledStations.map((item, idx) => ( + + {item.stationName} + { + setLabeledStations( + labeledStations.filter(s => s.stationName !== item.stationName), + ); + }} + /> + + ))} +
    + )} + { + if (!labeledStations.length) { + alert('์—ญ์„ ํ•˜๋‚˜ ์ด์ƒ ์„ ํƒํ•ด์ฃผ์„ธ์š”.'); + return; + } + + const formattedStations = labeledStations.map(station => ({ + stationName: station.stationName, + label: LABEL_OPTIONS.find(l => l.id === station.label)?.text || '', + })); + + updateUserFavoriteStations(formattedStations); -const SettingPage = () => { - return SettingPage; + setTimeout(() => { + pop(); + }, 500); + }} + > + ์ €์žฅํ•˜๊ธฐ + +
    +
    +
    + ); }; +const S = { + Container: styled.div` + padding: 0 0 0; + `, + Fixed: styled.div` + position: sticky; + top: 0; + left: 0; + width: 100%; + padding-top: 20px; + padding-bottom: 20px; + background-color: white; + `, + Headline: styled.h1` + ${({ theme }) => css` + font-size: 24px; + padding: 0 20px; + margin-bottom: 8px; + + & > b { + color: ${theme.colors['key-color']}; + } + `} + `, + Desc: styled.p` + color: #666; + margin-bottom: 24px; + padding: 0 20px; + `, +}; + +const SearchWrapper = styled.div` + position: relative; + margin-bottom: 12px; + padding: 0 20px; +`; + +const SearchInputIcon = styled(SearchIcon)` + position: absolute; + left: 32px; + top: 50%; + transform: translateY(-50%); + color: #999; +`; + +const SearchInput = styled.input` + width: 100%; + padding: 12px 16px 12px 40px; + border: 1px solid #e5e5e5; + border-radius: 8px; + font-size: 16px; + &::placeholder { + color: #999; + } +`; + +const LineNumberContainer = styled.div` + display: flex; + gap: 4px; + margin-right: 12px; +`; + +interface LineNumberProps { + lineNumber: number; +} + +const LineNumber = styled.div` + width: 24px; + height: 24px; + border-radius: 50%; + background-color: ${props => subwayLineHexColors(props.lineNumber)}; + color: white; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + flex-shrink: 0; +`; + +interface SearchResultsProps { + $isPending: boolean; +} + +const SearchResults = styled.div` + opacity: ${props => (props.$isPending ? 0.7 : 1)}; + transition: opacity 0.2s ease; + padding-bottom: 148px; +`; + +const SearchResultItem = styled.button` + width: 100%; + padding: 12px 20px 12px 20px; + text-align: left; + border: none; + background: none; + cursor: pointer; + display: flex; + align-items: center; + border-bottom: 1px solid #e5e5e5; + color: #000000; + + &:last-child { + border-bottom: none; + } +`; + +const StationContainer = styled.div` + border-bottom: 1px solid #f5f4f3; + + &:last-child { + border-bottom: none; + } +`; + +const LabelOptions = styled.div` + display: flex; + gap: 8px; + padding: 12px 16px; + overflow-x: auto; + white-space: nowrap; + width: 100%; + -ms-overflow-style: none; + scrollbar-width: none; + + &::-webkit-scrollbar { + display: none; + } +`; + +interface LabelButtonProps { + $isActive?: boolean; + $isDefault?: boolean; +} + +const LabelButton = styled.button` + padding: 8px 12px; + border-radius: 20px; + border: 1px solid ${props => (props.$isActive ? '#2ACF6C' : '#e5e5e5')}; + background-color: ${props => (props.$isActive ? '#2ACF6C' : 'white')}; + color: ${props => (props.$isActive ? 'white' : '#666')}; + font-size: 14px; + cursor: pointer; + display: flex; + align-items: center; + gap: 4px; + transition: all 0.2s ease; + flex-shrink: 0; + + & > svg > path { + fill: ${props => (props.$isActive ? 'white' : '')}; + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } +`; + +const LabelIndicator = styled.span` + margin-left: auto; + font-size: 16px; +`; + +const ButtonArea = styled.div` + position: fixed; + bottom: 0; + left: 0; + width: 100%; + background-color: white; + display: flex; + flex-direction: column; + justify-content: flex-end; + align-items: center; + gap: 16px; + padding-top: 16px; + padding-bottom: 32px; +`; + +const SubmitBtn = styled.button` + ${({ theme }) => css` + font-weight: 600; + color: white; + width: calc(100% - 40px); + height: 50px; + background-color: ${theme.colors['key-color']}; + border-radius: 8px; + `} +`; + export default SettingPage; diff --git a/services/ahhachul.com/src/pages/news/[newsId]/page.tsx b/services/ahhachul.com/src/pages/news/[newsId]/page.tsx new file mode 100644 index 000000000..accb43a90 --- /dev/null +++ b/services/ahhachul.com/src/pages/news/[newsId]/page.tsx @@ -0,0 +1,176 @@ +import { css } from '@emotion/react'; +import styled from '@emotion/styled'; +import { useActivity, type ActivityComponentType } from '@stackflow/react'; + +import { ShareIcon } from '@/assets/icons/system'; +import { LayoutComponent, UiComponent } from '@/components'; +import { subwayIconMap } from '@/constants'; + +const NewsDetailPage: ActivityComponentType<{ newsId: number }> = ({ + params: { newsId }, +}: { + params: { newsId: number }; +}) => { + console.log('newsId:', newsId); + const { isActive } = useActivity(); + const handleClickShare = () => {}; + const handleClickExternalLink = () => {}; + + return ( + + <> + +
    + + + + + + ๊ธฐ์‚ฌ์›๋ฌธ ์ด๋™ + + +
    +
    + + + + {'ํ•œ๋ฐค ๋‚ด๋ณต์ฐจ๋ฆผ ์ง€ํ•˜์ฒ ์—ญ ํ—ค๋งค๋˜ 90๋Œ€โ€ฆโ€˜์ด๊ฒƒโ€™ ๋•Œ๋ฌธ์— ๋ฌด์‚ฌ ๊ท€๊ฐ€ํ•œ๋ฐค ๋‚ด๋ณต์ฐจ๋ฆผ'} + + + + {subwayIconMap.get(2)} + {'2025.03.15. ์˜ค์ „ 8:21'} + + + + + {/* */} + + + + ํ•œ๋ฐค์ค‘ ์ง€ํ•˜์ฒ ์—ญ์„ ํ—ค๋งค๋˜ 90๋Œ€ ์น˜๋งค ๋…ธ์ธ์ด ๊ทผ๋ฌด ์ค‘์ด๋˜ ์—ญ ์ง์›์˜ ๋„์›€์„ ๋ฐ›์•„ ๋ฌด์‚ฌํžˆ + ์ง‘์œผ๋กœ ๋Œ์•„๊ฐ”๋‹ค.14์ผ ์„œ์šธ๊ตํ†ต๊ณต์‚ฌ์— ๋”ฐ๋ฅด๋ฉด, ์ง€๋‚œ 10์ผ ๋ฐค 23์‹œ 23๋ถ„๊ฒฝ 4ํ˜ธ์„  + ๋™๋Œ€๋ฌธ์—ญ์‚ฌ๋ฌธํ™”๊ณต์›์—ญ์— ๊ทผ๋ฌดํ•˜๋Š” ๋ผ๊ด‘์ˆ˜ ์ฐจ์žฅ์€ ํ์‡„ํšŒ๋กœ(CC)TV๋กœ ๊ฐ์‹œ ์—…๋ฌด๋ฅผ ํ•˜๋˜ ์ค‘ ๋‚ด๋ณต + ์ฐจ๋ฆผ์˜ ๋…ธ์ธ์ด 8๋ฒˆ ์ถœ๊ตฌ ๊ณ„๋‹จ์„ ๊ฑธ์–ด์„œ ๋‚ด๋ ค์˜ค๋Š” ๊ฒƒ์„ ๋ฐœ๊ฒฌํ–ˆ๋‹ค.์‹ ๊ณ ๋ฅผ ๋ฐ›๊ณ  ์ถœ๋™ํ•œ ๊ฒฝ์ฐฐ์€ + ๋…ธ์ธ์˜ โ€˜์น˜๋งค๋…ธ์ธ ์ธ์‹ํ‘œโ€™๋ฅผ ๋ฐœ๊ฒฌํ–ˆ๊ณ  ๋ณดํ˜ธ์ž์—๊ฒŒ ๋ฐ”๋กœ ์—ฐ๋ฝ์„ ์ทจํ–ˆ๋‹ค. ์ดํ›„ ๋…ธ์ธ์€ ๋ฌด์‚ฌํžˆ + ๊ฐ€์กฑ๊ณผ ๋งŒ๋‚ฌ๋‹ค.๋ผ ์ฐจ์žฅ์€ โ€œ์Œ€์Œ€ํ•œ ๋ฐค์— ํ™€๋กœ ๋ฐฐํšŒํ•˜๋Š” ๋…ธ์ธ์„ ์ฒ˜์Œ ๋ฐœ๊ฒฌํ–ˆ์„ ๋•Œ 7~8๋…„๊ฐ„ + ์น˜๋งค๋กœ ๊ณ ์ƒํ•˜์‹  ์–ด๋จธ๋‹ˆ๊ฐ€ ์ƒ๊ฐ๋‚˜ ๋‘์œ ๋ผ๋„ ํ•˜๋‚˜ ๋” ์ฑ™๊ฒจ๋“œ๋ฆฌ๊ณ  ์‹ถ์—ˆ๋‹คโ€๋ฉฐ โ€œ์ง์›์œผ๋กœ์„œ ๋งˆ๋•…ํžˆ + ํ•  ์ผ์„ ํ•œ ๊ฒƒ์ด๊ณ  ๋Šฆ์ง€ ์•Š๊ฒŒ ๋ฌด์‚ฌํžˆ ๊ฐ€์กฑ์˜ ํ’ˆ์œผ๋กœ ๋Œ์•„๊ฐ€ ๋‹คํ–‰โ€์ด๋ผ๊ณ  ๋งํ–ˆ๋‹ค. ๋งˆํ•ด๊ทผ + ์„œ์šธ๊ตํ†ต๊ณต์‚ฌ ์˜์—…๋ณธ๋ถ€์žฅ์€ โ€œ๋Šฆ์€ ๋ฐค์—๋„ ์„ฑ์‹คํžˆ ์ง๋ฌด๋ฅผ ์ˆ˜ํ–‰ํ•ด ์‹œ๋ฏผ์˜ ์•ˆ์ „์„ ์ง€ํ‚จ ์ง์›๊ณผ + ๋™๋Œ€๋ฌธ์—ญ์‚ฌ๋ฌธํ™”๊ณต์›์—ญ ์ง์›์—๊ฒŒ ๊ฐ์‚ฌํ•˜๋‹คโ€๋ฉฐ โ€œ์—ญ์‚ฌ ๋‚ด ์‹ค์ข…์ž ๋ฐœ์ƒ ์‹œ ๋ณดํ˜ธ์ž์˜ ํ’ˆ์œผ๋กœ + ํ•˜๋ฃจ๋นจ๋ฆฌ ๋Œ์•„๊ฐˆ ์ˆ˜ ์žˆ๋„๋ก ๋งค๋‰ด์–ผ์„ ๋ฐ”ํƒ•์œผ๋กœ ์ „ ์ง์›์ด ๋…ธ๋ ฅํ•˜๊ฒ ๋‹คโ€๋ผ๊ณ  ๋งํ–ˆ๋‹ค. + + + + +
    + ); +}; + +const ContentWrapper = styled.div` + padding: 20px; +`; + +const TitleWrapper = styled.div` + ${({ theme }) => css` + ${theme.fonts.headlineMedium}; + font-weight: 400; + color: #171717; + margin-bottom: 12px; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + `} +`; + +const MetaInfoWrapper = styled.div` + display: flex; + width: 100%; + align-items: center; + justify-content: space-between; + padding-bottom: 16px; + border-bottom: 1px solid ${({ theme }) => theme.colors.gray[20]}; +`; + +const AuthorDateWrapper = styled.div` + ${({ theme }) => css` + ${theme.fonts.bodyMedium}; + display: flex; + align-items: center; + gap: 4px; + `} +`; + +const DateText = styled.span` + color: ${({ theme }) => theme.colors.gray[70]}; +`; + +const SubwayLineWrapper = styled.div` + ${({ theme }) => css` + ${theme.fonts.labelMedium}; + display: flex; + align-items: center; + color: ${theme.colors.gray[90]}; + font-weight: 400; + `} +`; + +const ContentContainer = styled.div` + padding: 0 20px; +`; + +const TextContent = styled.div` + ${({ theme }) => css` + ${theme.fonts.bodyLargeSemi}; + font-family: 'Pretendard'; + color: ${theme.colors.gray[90]}; + white-space: pre-wrap; + word-break: break-all; + `} +`; + +const Padding = styled.div` + width: 100%; + height: 194px; +`; + +const HeaderContainer = styled.section` + ${({ theme }) => css` + position: fixed; + top: 0; + right: 16px; + background: ${theme.colors.white}; + z-index: ${theme.zIndex.header}; + height: 58px; + display: flex; + align-items: center; + justify-content: center; + gap: 15px; + `} +`; + +const ActionButton = styled.button` + ${({ theme }) => css` + ${theme.fonts.bodySmall}; + width: max-content; + display: flex; + align-items: center; + justify-content: center; + height: 30px; + border-radius: 3px; + border: 1px solid ${theme.colors.gray[40]}; + color: ${theme.colors.gray[90]}; + padding: 0 12px; + background-color: white; + `} +`; + +const ShareActionButton = styled.button` + width: max-content; + display: flex; + align-items: center; + justify-content: center; +`; + +export default NewsDetailPage; diff --git a/services/ahhachul.com/src/pages/news/index.ts b/services/ahhachul.com/src/pages/news/index.ts new file mode 100644 index 000000000..10056b221 --- /dev/null +++ b/services/ahhachul.com/src/pages/news/index.ts @@ -0,0 +1 @@ +export { default as NewsDetailPage } from './[newsId]/page'; diff --git a/services/ahhachul.com/src/pages/notification/page.tsx b/services/ahhachul.com/src/pages/notification/page.tsx index 515a2b544..7ad9c8746 100644 --- a/services/ahhachul.com/src/pages/notification/page.tsx +++ b/services/ahhachul.com/src/pages/notification/page.tsx @@ -1,7 +1,12 @@ import { LayoutComponent } from '@/components'; +import { MaintainContent } from '@/components/common'; const NotificationPage = () => { - return NotificationPage; + return ( + + + + ); }; export default NotificationPage; diff --git a/services/ahhachul.com/src/pages/subway/index.ts b/services/ahhachul.com/src/pages/subway/index.ts new file mode 100644 index 000000000..6d601e3ae --- /dev/null +++ b/services/ahhachul.com/src/pages/subway/index.ts @@ -0,0 +1,2 @@ +export { default as SubwayMapPage } from './map-page'; +export { default as SubwayTimelinePage } from './timeline-page'; diff --git a/services/ahhachul.com/src/pages/subway/map-page.tsx b/services/ahhachul.com/src/pages/subway/map-page.tsx new file mode 100644 index 000000000..916e5b978 --- /dev/null +++ b/services/ahhachul.com/src/pages/subway/map-page.tsx @@ -0,0 +1,12 @@ +import { LayoutComponent } from '@/components'; +import { MaintainContent } from '@/components/common'; + +const SubwayMapPage = () => { + return ( + + + + ); +}; + +export default SubwayMapPage; diff --git a/services/ahhachul.com/src/pages/subway/timeline-page.tsx b/services/ahhachul.com/src/pages/subway/timeline-page.tsx new file mode 100644 index 000000000..8e31ac84f --- /dev/null +++ b/services/ahhachul.com/src/pages/subway/timeline-page.tsx @@ -0,0 +1,12 @@ +import { LayoutComponent } from '@/components'; +import { MaintainContent } from '@/components/common'; + +const SubwayTimeLinePage = () => { + return ( + + + + ); +}; + +export default SubwayTimeLinePage; diff --git a/services/ahhachul.com/src/pages/talk/page.tsx b/services/ahhachul.com/src/pages/talk/page.tsx index 96f953cb5..d8607ce90 100644 --- a/services/ahhachul.com/src/pages/talk/page.tsx +++ b/services/ahhachul.com/src/pages/talk/page.tsx @@ -1,7 +1,12 @@ import { LayoutComponent } from '@/components'; +import { MaintainContent } from '@/components/common'; const TalkPage = () => { - return TalkPage; + return ( + + + + ); }; export default TalkPage; diff --git a/services/ahhachul.com/src/render.tsx b/services/ahhachul.com/src/render.tsx new file mode 100644 index 000000000..c45ea130c --- /dev/null +++ b/services/ahhachul.com/src/render.tsx @@ -0,0 +1,16 @@ +import { createRoot } from 'react-dom/client'; + +import App from './App'; +import { Provider } from './contexts'; + +function render() { + const root = createRoot(document.getElementById('root')!); + + root.render( + + + , + ); +} + +export { render }; diff --git a/services/ahhachul.com/src/services/comment.ts b/services/ahhachul.com/src/services/comment.ts new file mode 100644 index 000000000..6cc861d65 --- /dev/null +++ b/services/ahhachul.com/src/services/comment.ts @@ -0,0 +1,47 @@ +import { useMutation } from '@tanstack/react-query'; + +import * as api from '@/apis/request'; +import type { ApiResponse, Comment } from '@/types'; + +export const usePostComment = () => { + const afterSubmitFailed = (error: Error) => { + // ํ† ์ŠคํŠธ ๋„์–ด์ฃผ๊ณ  ๋’ค๋กœ ๊ฐ€๊ธฐ + console.log('error with toast:', error, 'ํ† ์ŠคํŠธ ๋„์–ด์ฃผ๊ณ  ๋’ค๋กœ ๊ฐ€๊ธฐ'); + window.alert('๋Œ“๊ธ€ ์ž‘์„ฑํ•˜๋‹ค๊ฐ€ ์—๋Ÿฌ ๋ฐœ์ƒ'); + }; + + return useMutation({ + mutationFn: api.postComment, + onError: afterSubmitFailed, + }); +}; + +export const useDeleteComment = (articleId: number) => { + const afterSubmitSuccess = (res: ApiResponse>) => { + console.log('res:', res); + console.log('articleId:', articleId); + }; + const afterSubmitFailed = (error: Error) => { + // ํ† ์ŠคํŠธ ๋„์–ด์ฃผ๊ณ  ๋’ค๋กœ ๊ฐ€๊ธฐ + console.log('error with toast:', error, 'ํ† ์ŠคํŠธ ๋„์–ด์ฃผ๊ณ  ๋’ค๋กœ ๊ฐ€๊ธฐ'); + window.alert('๋Œ“๊ธ€ ์‹์ œํ•˜๋‹ค๊ฐ€ ์—๋Ÿฌ ๋ฐœ์ƒ'); + }; + + return useMutation({ + mutationFn: api.deleteComment, + onError: afterSubmitFailed, + onSuccess: afterSubmitSuccess, + }); +}; + +export const useUpdateComment = () => { + const afterSubmitFailed = (error: Error) => { + console.log('error with toast:', error, 'ํ† ์ŠคํŠธ ๋„์–ด์ฃผ๊ณ  ๋’ค๋กœ ๊ฐ€๊ธฐ'); + window.alert('๋Œ“๊ธ€ ์ˆ˜์ •ํ•˜๋‹ค๊ฐ€ ์—๋Ÿฌ ๋ฐœ์ƒ'); + }; + + return useMutation({ + mutationFn: api.updateComment, + onError: afterSubmitFailed, + }); +}; diff --git a/services/ahhachul.com/src/services/community.ts b/services/ahhachul.com/src/services/community.ts index e08260022..d857f0b36 100644 --- a/services/ahhachul.com/src/services/community.ts +++ b/services/ahhachul.com/src/services/community.ts @@ -5,9 +5,14 @@ import { useSuspenseQuery, } from '@tanstack/react-query'; +import { removeFalsyValues } from '@ahhachul/utils'; + import * as api from '@/apis/request'; import { TIMESTAMP } from '@/constants'; +import { TOAST_MSG } from '@/constants/toast'; +import { useToast } from '@/hooks/useToast'; import { useFlow } from '@/stackflow'; +import { useUserStationStore } from '@/stores/subway'; import { CommunityType, type CommunityForm, @@ -15,7 +20,7 @@ import { type CommunityListParams, type SubwayLineFilterOptions, } from '@/types'; -import * as formatter from '@/utils/format'; +import { formatSubwayFilterOption, getFirstParentLineId } from '@/utils'; export const communityKeys = { all: ['community'] as const, @@ -29,13 +34,16 @@ export const communityKeys = { }; export const useFetchCommunityList = (filters: CommunityListParams) => { - const favoriteLine = 2; - const req = formatter.deleteObjectKeyWithEmptyValue( + const state = useUserStationStore(state => state); + const userStations = getFirstParentLineId(state.userStations); + + const req = removeFalsyValues( { - categoryType: filters.categoryType, - content: filters.content, writer: filters.writer, - subwayLineId: formatter.formatSubwayFilterOption(filters.subwayLineId, favoriteLine), + content: filters.content, + hashTag: filters.hashTag, + categoryType: filters.categoryType, + subwayLineIds: formatSubwayFilterOption(filters.subwayLineId, userStations), }, { removeZero: true, removeEmptyStrings: true }, ) as CommunityListParams; @@ -54,7 +62,7 @@ export const useFetchCommunityList = (filters: CommunityListParams { const { pop, push } = useFlow(); - // const { addToast } = useToast(); + const { addToast } = useToast(); const queryClient = useQueryClient(); @@ -72,8 +80,9 @@ export const useCreateCommunity = () => { }); }, 500); }, - onError: () => { - // addToast(TOAST_MSG.WARNING.CREATE_FAIL); + onError: error => { + console.log('error:', error); + addToast(TOAST_MSG.WARNING.CREATE_FAIL, 'warning'); }, }); }; @@ -123,3 +132,20 @@ export const useEditCommunity = (id: number, categoryType: CommunityType) => { }, }); }; + +export const useDeleteCommunity = () => { + const afterSubmitSuccess = (res: any) => { + console.log('res:', res); + }; + const afterSubmitFailed = (error: Error) => { + // ํ† ์ŠคํŠธ ๋„์–ด์ฃผ๊ณ  ๋’ค๋กœ ๊ฐ€๊ธฐ + console.log('error with toast:', error, 'ํ† ์ŠคํŠธ ๋„์–ด์ฃผ๊ณ  ๋’ค๋กœ ๊ฐ€๊ธฐ'); + window.alert('๋Œ“๊ธ€ ์‹์ œํ•˜๋‹ค๊ฐ€ ์—๋Ÿฌ ๋ฐœ์ƒ'); + }; + + return useMutation({ + mutationFn: api.deleteCommunity, + onError: afterSubmitFailed, + onSuccess: afterSubmitSuccess, + }); +}; diff --git a/services/ahhachul.com/src/services/complaint.ts b/services/ahhachul.com/src/services/complaint.ts index cb0ff5c3b..e3f0eb063 100644 --- a/services/ahhachul.com/src/services/complaint.ts +++ b/services/ahhachul.com/src/services/complaint.ts @@ -1 +1,130 @@ -export {}; +import { + useMutation, + useQueryClient, + useSuspenseInfiniteQuery, + useSuspenseQuery, +} from '@tanstack/react-query'; + +import { removeFalsyValues } from '@ahhachul/utils'; + +import * as api from '@/apis/request'; +import { TIMESTAMP } from '@/constants'; +import { TOAST_MSG } from '@/constants/toast'; +import { useToast } from '@/hooks/useToast'; +import { useFlow } from '@/stackflow'; +import { useUserStationStore } from '@/stores/subway'; +import { SubwayLineFilterOptions } from '@/types'; +import type { ComplaintForm, ComplaintListParams } from '@/types/complaint'; +import { formatSubwayFilterOption, getFirstParentLineId } from '@/utils'; +import { extractTextFromLexical } from '@/utils/lexical'; + +export const complaintKeys = { + all: ['complaint'] as const, + lists: () => [...complaintKeys.all, 'list'] as const, + list: (filters: (string | number)[]) => [...complaintKeys.lists(), ...filters] as const, + details: () => [...complaintKeys.all, 'detail'] as const, + detail: (id: number) => [...complaintKeys.details(), id] as const, + comments(id: number) { + return [...this.detail(id), 'comment-list'] as const; + }, +}; + +export const useFetchComplaintList = (filters: ComplaintListParams) => { + const state = useUserStationStore(state => state); + const userStations = getFirstParentLineId(state.userStations); + + const req = removeFalsyValues( + { + keyword: filters.keyword, + subwayLineIds: formatSubwayFilterOption(filters.subwayLineId, userStations), + }, + { removeZero: true, removeEmptyStrings: true }, + ) as ComplaintListParams; + + return useSuspenseInfiniteQuery({ + initialPageParam: '', + queryKey: complaintKeys.list(Object.values(req)), + queryFn: ({ pageParam = filters.pageToken }) => + api.fetchComplaintList({ + ...req, + ...(pageParam && { pageToken: pageParam }), + }), + getNextPageParam: lastPage => lastPage.result.pageToken, + select: res => { + res.pages[res.pages.length - 1].result.data = res.pages[res.pages.length - 1].result.data.map( + v => ({ + ...v, + title: extractTextFromLexical(v.content, v.complaintType).slice(0, 20), + }), + ); + return res; + }, + }); +}; + +export const useCreateComplaint = () => { + const { pop, push } = useFlow(); + const { addToast } = useToast(); + + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (req: ComplaintForm) => api.createComplaint(req), + onSuccess: res => { + pop(); + + queryClient.invalidateQueries({ + queryKey: complaintKeys.lists(), + }); + setTimeout(() => { + push('ComplaintDetailPage', { + id: res.result.id, + }); + }, 500); + }, + onError: () => { + addToast(TOAST_MSG.WARNING.CREATE_FAIL, 'warning'); + }, + }); +}; + +export const useFetchComplaintDetail = (id: number) => + useSuspenseQuery({ + queryKey: complaintKeys.detail(id), + queryFn: () => api.fetchComplaintDetail(id), + staleTime: 5 * TIMESTAMP.MINUTE, // 5๋ถ„ + select: res => { + return { + ...res.data.result, + title: extractTextFromLexical(res.data.result.content, res.data.result.complaintType).slice( + 0, + 20, + ), + }; + }, + }); + +export const useFetchComplaintCommentList = (id: number) => + useSuspenseQuery({ + queryKey: complaintKeys.comments(id), + queryFn: () => api.fetchComplaintCommentList(id), + staleTime: 5 * TIMESTAMP.MINUTE, //5๋ถ„ + select: res => res.data.result, + }); + +export const useDeleteComplaint = () => { + const afterSubmitSuccess = (res: any) => { + console.log('res:', res); + }; + const afterSubmitFailed = (error: Error) => { + // ํ† ์ŠคํŠธ ๋„์–ด์ฃผ๊ณ  ๋’ค๋กœ ๊ฐ€๊ธฐ + console.log('error with toast:', error, 'ํ† ์ŠคํŠธ ๋„์–ด์ฃผ๊ณ  ๋’ค๋กœ ๊ฐ€๊ธฐ'); + window.alert('๋Œ“๊ธ€ ์‹์ œํ•˜๋‹ค๊ฐ€ ์—๋Ÿฌ ๋ฐœ์ƒ'); + }; + + return useMutation({ + mutationFn: api.deleteComplaint, + onError: afterSubmitFailed, + onSuccess: afterSubmitSuccess, + }); +}; diff --git a/services/ahhachul.com/src/services/lostFound.ts b/services/ahhachul.com/src/services/lostFound.ts index c8fcab00a..d01e62ff8 100644 --- a/services/ahhachul.com/src/services/lostFound.ts +++ b/services/ahhachul.com/src/services/lostFound.ts @@ -5,9 +5,14 @@ import { useSuspenseQuery, } from '@tanstack/react-query'; +import { removeFalsyValues } from '@ahhachul/utils'; + import * as api from '@/apis/request'; import { TIMESTAMP } from '@/constants'; +import { TOAST_MSG } from '@/constants/toast'; +import { useToast } from '@/hooks/useToast'; import { useFlow } from '@/stackflow'; +import { useUserStationStore } from '@/stores/subway'; import { LostFoundType, type LostFoundForm, @@ -15,7 +20,7 @@ import { type LostFoundListParams, type SubwayLineFilterOptions, } from '@/types'; -import * as formatter from '@/utils/format'; +import { formatSubwayFilterOption, getFirstParentLineId } from '@/utils'; export const lostFoundKeys = { all: ['lostFound'] as const, @@ -29,12 +34,14 @@ export const lostFoundKeys = { }; export const useFetchLostFoundList = (filters: LostFoundListParams) => { - const favoriteLine = 3; - const req = formatter.deleteObjectKeyWithEmptyValue( + const state = useUserStationStore(state => state); + const userStations = getFirstParentLineId(state.userStations); + + const req = removeFalsyValues( { lostType: filters.lostType, keyword: filters.keyword, - subwayLineId: formatter.formatSubwayFilterOption(filters.subwayLineId, favoriteLine), + subwayLineIds: formatSubwayFilterOption(filters.subwayLineId, userStations), }, { removeZero: true, removeEmptyStrings: true }, ) as LostFoundListParams; @@ -53,7 +60,7 @@ export const useFetchLostFoundList = (filters: LostFoundListParams { const { pop, push } = useFlow(); - // const { addToast } = useToast(); + const { addToast } = useToast(); const queryClient = useQueryClient(); @@ -72,7 +79,7 @@ export const useCreateLostFound = () => { }, 500); }, onError: () => { - // addToast(TOAST_MSG.WARNING.CREATE_FAIL); + addToast(TOAST_MSG.WARNING.CREATE_FAIL, 'warning'); }, }); }; @@ -122,3 +129,20 @@ export const useEditLostFound = (id: number, lostType: LostFoundType) => { }, }); }; + +export const useDeleteLostFound = () => { + const afterSubmitSuccess = (res: any) => { + console.log('res:', res); + }; + const afterSubmitFailed = (error: Error) => { + // ํ† ์ŠคํŠธ ๋„์–ด์ฃผ๊ณ  ๋’ค๋กœ ๊ฐ€๊ธฐ + console.log('error with toast:', error, 'ํ† ์ŠคํŠธ ๋„์–ด์ฃผ๊ณ  ๋’ค๋กœ ๊ฐ€๊ธฐ'); + window.alert('๋Œ“๊ธ€ ์‹์ œํ•˜๋‹ค๊ฐ€ ์—๋Ÿฌ ๋ฐœ์ƒ'); + }; + + return useMutation({ + mutationFn: api.deleteLostFound, + onError: afterSubmitFailed, + onSuccess: afterSubmitSuccess, + }); +}; diff --git a/services/ahhachul.com/src/services/subway.ts b/services/ahhachul.com/src/services/subway.ts new file mode 100644 index 000000000..4dea01c0b --- /dev/null +++ b/services/ahhachul.com/src/services/subway.ts @@ -0,0 +1,38 @@ +import { useQuery } from '@tanstack/react-query'; + +import { fetchSubwayLines, fetchTrainInfo } from '@/apis/request/subway'; +import { TIMESTAMP } from '@/constants'; +import { APITrainInfoParams } from '@/types'; +import { formatSubwayLineInfo } from '@/utils'; + +export const subwayKeys = { + all: ['subway'] as const, + subwayLine: () => [...subwayKeys.all, 'subway-line'] as const, + trains: () => [...subwayKeys.all, 'list'] as const, + train: (filters: (string | number)[]) => [...subwayKeys.trains(), ...filters] as const, +}; + +export const useFetchSubwayLines = () => + useQuery({ + queryKey: subwayKeys.subwayLine(), + queryFn: fetchSubwayLines, + gcTime: Infinity, + staleTime: Infinity, + refetchOnMount: false, + refetchOnWindowFocus: false, + refetchOnReconnect: false, + select: res => { + return formatSubwayLineInfo(res.data.result); + }, + }); + +export const useFetchTrainInfo = (params: APITrainInfoParams) => { + return useQuery({ + refetchInterval: 30 * TIMESTAMP.SECOND, + queryKey: subwayKeys.train(Object.values(params)), + queryFn: () => fetchTrainInfo(params), + select: res => { + return res.data.result; + }, + }); +}; diff --git a/services/ahhachul.com/src/services/user.ts b/services/ahhachul.com/src/services/user.ts index 7591b809e..b2801a799 100644 --- a/services/ahhachul.com/src/services/user.ts +++ b/services/ahhachul.com/src/services/user.ts @@ -1,11 +1,13 @@ -import { useQuery } from '@tanstack/react-query'; +import { useMutation, useQuery } from '@tanstack/react-query'; import * as api from '@/apis/request'; import { useAuth } from '@/contexts'; +import { useUserStationStore } from '@/stores/subway'; +import { ApiResponse, UserFavoriteStations } from '@/types'; export const userKeys = { all: ['user'] as const, - infos: () => [...userKeys.all, 'infos'] as const, + info: () => [...userKeys.all, 'info'] as const, stations: () => [...userKeys.all, 'stations'] as const, }; @@ -13,9 +15,14 @@ export const useFetchUserProfile = () => { const { authService } = useAuth(); return useQuery({ - queryKey: userKeys.infos(), + queryKey: userKeys.info(), enabled: authService.isAuthenticated, queryFn: api.fetchUserProfile, + gcTime: 1000 * 60 * 60, + staleTime: 1000 * 60 * 30, + refetchOnMount: false, + refetchOnReconnect: false, + refetchOnWindowFocus: false, }); }; @@ -26,5 +33,30 @@ export const useFetchUserFavoriteStations = () => { queryKey: userKeys.stations(), enabled: authService.isAuthenticated, queryFn: api.fetchUserFavoriteStations, + staleTime: 1000 * 60 * 30, + gcTime: 1000 * 60 * 60, + refetchOnMount: false, + refetchOnWindowFocus: false, + refetchOnReconnect: false, + }); +}; + +export const useUserFavoriteStations = () => { + const setUserStations = useUserStationStore(state => state.setUserStations); + + const afterSubmitSuccess = (res: ApiResponse) => { + setUserStations(res.result.stationInfoList); + }; + + const afterSubmitFailed = (error: Error) => { + // ํ† ์ŠคํŠธ ๋„์–ด์ฃผ๊ณ  ๋’ค๋กœ ๊ฐ€๊ธฐ + console.log('error with toast:', error, 'ํ† ์ŠคํŠธ ๋„์–ด์ฃผ๊ณ  ๋’ค๋กœ ๊ฐ€๊ธฐ'); + // window.alert('์ฆ๊ฒจ์ฐพ๋Š” ์ง€ํ•˜์ฒ ์—ญ ์„ค์ •์„ ์„œ๋ฒ„์— ๋ณด๋‚ด๋Š”๋ฐ ์—๋Ÿฌ ๋ฐœ์ƒ'); + }; + + return useMutation({ + mutationFn: api.createUserFavoriteStations, + onError: afterSubmitFailed, + onSuccess: afterSubmitSuccess, }); }; diff --git a/services/ahhachul.com/src/stackflow.config.ts b/services/ahhachul.com/src/stackflow.config.ts index a3519f20f..34e4e60b9 100644 --- a/services/ahhachul.com/src/stackflow.config.ts +++ b/services/ahhachul.com/src/stackflow.config.ts @@ -1,6 +1,5 @@ import { basicUIPlugin } from '@stackflow/plugin-basic-ui'; import { historySyncPlugin } from '@stackflow/plugin-history-sync'; -// import { preloadPlugin } from '@stackflow/plugin-preload'; import { basicRendererPlugin } from '@stackflow/plugin-renderer-basic'; import { PATH } from '@/constants/path'; @@ -25,6 +24,8 @@ export const stackflowPlugin = [ // my pages MyPage: PATH.me.home, + SettingPage: PATH.me.setting, + MyAccountPage: PATH.me.account, // home page HomePage: PATH.home, @@ -43,7 +44,7 @@ export const stackflowPlugin = [ // complaint pages ComplaintPage: PATH.complaint.home, - ComplaintListPage: PATH.complaint.list, + ComplaintPanelPage: PATH.complaint.list, NewComplaintPage: PATH.complaint.new, EditComplaintPage: PATH.complaint.edit, ComplaintDetailPage: PATH.complaint.detail, @@ -58,12 +59,14 @@ export const stackflowPlugin = [ EditCommentPage: PATH.comment.edit, NewCommentReplyPage: PATH.comment.reply, + + NewsDetailPage: PATH.news.detail, + + HashtagPage: PATH.hashtag.home, + + SubwayMapPage: PATH.subway.map, + SubwayTimelinePage: PATH.subway.timeline, }, fallbackActivity: () => 'HomePage', }), - // preloadPlugin({ - // loaders: { - // CommunityDetail: CommunityDetail(queryClient), - // }, - // }), ]; diff --git a/services/ahhachul.com/src/stores/comment.ts b/services/ahhachul.com/src/stores/comment.ts new file mode 100644 index 000000000..d3874bca9 --- /dev/null +++ b/services/ahhachul.com/src/stores/comment.ts @@ -0,0 +1,24 @@ +import { create } from 'zustand'; +import { useShallow } from 'zustand/react/shallow'; + +import type { CommentList } from '@/types'; + +interface TempCommentState { + comment: CommentList['comments'] | null; + setComment: (comment: CommentList['comments'] | null) => void; + clearComment: () => void; +} + +const useTempAuthStore = create(set => ({ + comment: null, + setComment: comment => set({ comment }), + clearComment: () => set({ comment: null }), +})); + +export const useTempComment = () => { + const tempComment = useTempAuthStore(useShallow(state => state.comment)); + const setTempComment = useTempAuthStore(useShallow(state => state.setComment)); + const clearTempComment = useTempAuthStore(useShallow(state => state.clearComment)); + + return { tempComment, setTempComment, clearTempComment }; +}; diff --git a/services/ahhachul.com/src/stores/subway.ts b/services/ahhachul.com/src/stores/subway.ts new file mode 100644 index 000000000..698e8ad81 --- /dev/null +++ b/services/ahhachul.com/src/stores/subway.ts @@ -0,0 +1,24 @@ +import { create } from 'zustand'; +import { persist } from 'zustand/middleware'; + +import { defaultStationList } from '@/constants'; +import type { UserStationList } from '@/types'; + +export interface IUserStationStore { + userStations: UserStationList; + setUserStations: (updated: UserStationList) => void; +} + +export const useUserStationStore = create( + persist( + set => ({ + userStations: defaultStationList, + setUserStations: (updated: UserStationList) => { + set({ userStations: updated }); + }, + }), + { + name: 'ahhachul-user-station-list', + }, + ), +); diff --git a/services/ahhachul.com/src/stores/toast.ts b/services/ahhachul.com/src/stores/toast.ts new file mode 100644 index 000000000..bc41e5c23 --- /dev/null +++ b/services/ahhachul.com/src/stores/toast.ts @@ -0,0 +1,39 @@ +import { v4 as uuidv4 } from 'uuid'; +import { create } from 'zustand'; + +export type ToastType = 'success' | 'warning' | 'info' | 'error'; + +export interface Toast { + id: string; + message: string; + type: ToastType; + duration?: number; +} + +interface ToastState { + toasts: Toast[]; + addToast: (message: string, type: ToastType, duration?: number) => void; + removeToast: (id: string) => void; +} + +export const useToastStore = create(set => ({ + toasts: [], + addToast: (message, type, duration = 3000) => { + const id = uuidv4(); + set(state => ({ + toasts: [...state.toasts, { id, message, type, duration }], + })); + + // Auto-remove toast after duration + setTimeout(() => { + set(state => ({ + toasts: state.toasts.filter(toast => toast.id !== id), + })); + }, duration); + }, + removeToast: id => { + set(state => ({ + toasts: state.toasts.filter(toast => toast.id !== id), + })); + }, +})); diff --git a/services/ahhachul.com/src/styles/globalStyles.ts b/services/ahhachul.com/src/styles/globalStyles.ts index c11c2e9ae..5d08aa24d 100644 --- a/services/ahhachul.com/src/styles/globalStyles.ts +++ b/services/ahhachul.com/src/styles/globalStyles.ts @@ -5,12 +5,23 @@ import { theme } from './theme'; const globalStyles = css` @font-face { font-family: 'Pretendard'; - src: url('../assets/fonts/pretendard-variable.woff2') format('woff2'); + src: url('/fonts/pretendard-variable.woff2') format('woff2'); font-weight: 45 920; font-style: normal; font-display: swap; } + * { + -webkit-tap-highlight-color: rgba(255, 255, 255, 0); + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-user-drag: none; + } + html, body, div, @@ -136,7 +147,7 @@ const globalStyles = css` body { ${theme.fonts.bodyMedium}; min-height: -webkit-fill-available; - font-family: Pretendard, system-ui; + font-family: 'Pretendard', system-ui; color: ${theme.colors.black}; line-height: 1; background-color: ${theme.colors.white}; diff --git a/services/ahhachul.com/src/styles/keyframes.ts b/services/ahhachul.com/src/styles/keyframes.ts index 23faab2b7..b97acda15 100644 --- a/services/ahhachul.com/src/styles/keyframes.ts +++ b/services/ahhachul.com/src/styles/keyframes.ts @@ -4,3 +4,15 @@ export const fadeIn = keyframes` 0% { opacity: 0; } 100% { opacity: 1; } `; + +export const fade = keyframes` + 0% { + opacity: 1; + } + 50% { + opacity: 0; + } + 100% { + opacity: 1; + } +`; diff --git a/services/ahhachul.com/src/types/common.ts b/services/ahhachul.com/src/types/common.ts index b5239b15c..c4a44d197 100644 --- a/services/ahhachul.com/src/types/common.ts +++ b/services/ahhachul.com/src/types/common.ts @@ -64,6 +64,7 @@ export type Comment = { status: CommentStatus; upperCommentId: number | null; likeCnt?: number; + isPrivate?: boolean; }; export type CommentList = { diff --git a/services/ahhachul.com/src/types/community.ts b/services/ahhachul.com/src/types/community.ts index 2b6d8fc91..fbd521cc5 100644 --- a/services/ahhachul.com/src/types/community.ts +++ b/services/ahhachul.com/src/types/community.ts @@ -11,7 +11,7 @@ import type { export enum CommunityType { HOT = 'HOT', FREE = 'FREE', - HUMOR = 'HUMOR', + ISSUE = 'ISSUE', INSIGHT = 'INSIGHT', } @@ -43,11 +43,12 @@ export interface CommunityListParams extends Partial extends Partial { + subwayLineId: TSubwayLine; + keyword?: string; +} + +export interface ComplaintForm { + title: string; + content: string; + subwayLineId: number; + complaintType: ComplaintType; + shortContentType: ShortComplaintType; + images: File[]; +} + +export type ComplaintFilterKeys = 'subwayLineId'; + +export type ComplaintFilterValues = { + subwayLineId: SubwayLineFilterOptions; +}; + +export type ComplaintFilters = { + [K in ComplaintFilterKeys]: ComplaintFilterValues[K]; +}; diff --git a/services/ahhachul.com/src/types/filter.ts b/services/ahhachul.com/src/types/filter.ts index 07e4c2beb..30f155d6c 100644 --- a/services/ahhachul.com/src/types/filter.ts +++ b/services/ahhachul.com/src/types/filter.ts @@ -2,4 +2,7 @@ import type { TypeActivities } from '@/stackflow'; import type { KeyOf } from './common'; -export type AppUniqueFilterId = Extract, 'CommunityPage' | 'LostFoundPage'>; +export type AppUniqueFilterId = Extract< + KeyOf, + 'CommunityPage' | 'LostFoundPage' | 'ComplaintPage' | 'HashtagPage' +>; diff --git a/services/ahhachul.com/src/types/nativeBridge.ts b/services/ahhachul.com/src/types/nativeBridge.ts index 8dac32b9f..626cf09de 100644 --- a/services/ahhachul.com/src/types/nativeBridge.ts +++ b/services/ahhachul.com/src/types/nativeBridge.ts @@ -10,7 +10,7 @@ export type NativeBridgeType = { share: (link: string) => void; callPhone: (number: string) => void; openExternalLink: (link: string) => void; - sendTextMessage: (number: string) => void; + sendTextMessage: (number: string, message?: string) => void; }; receive: { deviceInfo: () => Promise<{ diff --git a/services/ahhachul.com/src/types/share.ts b/services/ahhachul.com/src/types/share.ts index d89943a1a..6187cf434 100644 --- a/services/ahhachul.com/src/types/share.ts +++ b/services/ahhachul.com/src/types/share.ts @@ -2,7 +2,7 @@ import { WEB_SERVICE_URL } from '@/constants'; export type ShareablePageMap = { CommunityDetailPage: 'community'; - LostFoundDetailPage: 'lostFound'; + LostFoundDetailPage: 'lost-found'; ComplaintDetailPage: 'complaint'; }; diff --git a/services/ahhachul.com/src/types/subway.ts b/services/ahhachul.com/src/types/subway.ts index 13760594f..7da2ae212 100644 --- a/services/ahhachul.com/src/types/subway.ts +++ b/services/ahhachul.com/src/types/subway.ts @@ -35,3 +35,123 @@ export type SubwayLineKrType = | '์ˆ˜์ธ๋ถ„๋‹น์„ ' | '๊ฒฝ์˜์ค‘์•™์„ ' | '์šฐ์ด์‹ ์„ค๊ฒฝ์ „์ฒ '; + +export interface UserStation { + label: string; + stationId: number; + stationName: string; + subwayLineInfoList: { + subwayLineId: SubwayLineType; + subwayLineName: string; + }[]; +} + +export type UserStationList = UserStation[]; + +export type UserFavoriteStations = { + stationInfoList: UserStationList; +}; + +export type WithSubwayLineId = { + subwayLineId: number | SubwayLineType; +}; + +export type WithSubwayStationId = { + stationId: number; +}; + +export interface WithSubwayTrainId { + trainNo: number; +} + +export interface APITrainInfoParams extends WithSubwayLineId, WithSubwayStationId {} + +/** + * ์—ด์ฐจ์˜ ํ˜„์žฌ ๋„์ฐฉ ์ƒํƒœ๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ํƒ€์ž… + * + * @value {'ENTER'} ์ง„์ž… + * @value {'ARRIVE'} ๋„์ฐฉ + * @value {'DEPARTURE'} ์ถœ๋ฐœ + * @value {'BEFORE_STATION_DEPARTURE'} ์ „์—ญ์ถœ๋ฐœ + * @value {'BEFORE_STATION_ARRIVE'} ์ „์—ญ๋„์ฐฉ + * @value {'BEFORE_STATION_ENTER'} ์ „์—ญ์ง„์ž… + * @value {'RUNNING'} ์šดํ–‰์ค‘ + */ +export enum CurrentTrainArrivalType { + ENTER = 'ENTER', + ARRIVE = 'ARRIVE', + DEPARTURE = 'DEPARTURE', + BEFORE_STATION_DEPARTURE = 'BEFORE_STATION_DEPARTURE', + BEFORE_STATION_ARRIVE = 'BEFORE_STATION_ARRIVE', + BEFORE_STATION_ENTER = 'BEFORE_STATION_ENTER', + RUNNING = 'RUNNING', +} + +export enum UpDownType { + UP = 'UP', + DOWN = 'DOWN', +} + +/** + * Train interface + * + * @property {string} trainNum - ์—ด์ฐจ id + * @property {UpDownType} upDownType - ์ƒํ•˜ํ–‰์„ ๊ตฌ๋ถ„ {@link UpDownType} + * @property {string} nextStationDirection - ๋‹ค์Œ ์ •๋ฅ˜์žฅ ๋ฐฉํ–ฅ + * @property {string} destinationStationDirection - ๋ชฉ์ ์ง€ ๋ฐฉํ–ฅ + * @property {number} currentArrivalTime - ์—ด์ฐจ ๋„์ฐฉ ์‹œ๊ฐ„ + * @property {CurrentTrainArrivalType} currentTrainArrivalCode - ํ•ด๋‹น ์—ด์ฐจ ํ˜„์žฌ ์œ„์น˜ ์ฝ”๋“œ {@link CurrentTrainArrivalType} + */ +export interface ITrain { + trainNum: number; + upDownType: UpDownType; + nextStationDirection: string; + destinationStationDirection: string; + currentArrivalTime: number; + currentTrainArrivalCode: CurrentTrainArrivalType; +} + +/** + * ์—ด์ฐจ ์นธ์˜ ํ˜„์žฌ ํ˜ผ์žก๋„ ์ƒ‰์ƒ์„ ๋‚˜ํƒ€๋‚ด๋Š” ํƒ€์ž… + * + * @value {'SMOOTH'} ์›ํ™œ + * @value {'MODERATE'} ๋ณดํ†ต + * @value {'CONGESTED'} ํ˜ผ์žก + * @value {'VERY_CONGESTED'} ๋งค์šฐ ํ˜ผ์žก + */ +export type CongestionColorType = 'SMOOTH' | 'MODERATE' | 'CONGESTED' | 'VERY_CONGESTED'; + +/** + * Congestion interface + * + * @property {string} sectionNo - ์—ด์ฐจ ์นธ id + * @property {CongestionColorType} congestionColor - ํ˜ผ์žก๋„ ์ƒ‰์ƒ ํƒ€์ž… {@link CongestionColorType} + */ +export interface Congestion { + sectionNo: number; + congestionColor: CongestionColorType; +} + +export interface StationServerModel { + id: number; + name: string; +} + +export interface StationClientModel { + stationId: number; + parentLineNames: string; + parentLineId: number; +} + +export type Stations = { [key: string]: StationClientModel[] }; + +export interface SubwayLine { + id: number; + name: string; + phoneNumber: string; + stations: StationServerModel[]; +} + +export interface SubwayLineServerModel { + subwayLines: SubwayLine[]; +} diff --git a/services/ahhachul.com/src/types/user.ts b/services/ahhachul.com/src/types/user.ts index 0f5d5d211..0facfa73b 100644 --- a/services/ahhachul.com/src/types/user.ts +++ b/services/ahhachul.com/src/types/user.ts @@ -19,4 +19,11 @@ export interface UserProfileResponseDto { email?: string; gender?: Gender; ageRange?: AgeRange; + imageUrl?: string; +} + +export interface APIUpdateUserResponse { + nickname: string; + gender: string | null; + ageRange: string | null; } diff --git a/services/ahhachul.com/src/utils/authService.ts b/services/ahhachul.com/src/utils/authService.ts index 832d6c9d5..d3dfe6e6b 100644 --- a/services/ahhachul.com/src/utils/authService.ts +++ b/services/ahhachul.com/src/utils/authService.ts @@ -1,5 +1,3 @@ -import Cookies from 'js-cookie'; - import { CRYPTO_SECRET_KEY } from '@/constants/auth'; import type { IAuthStore } from '@/types'; @@ -51,7 +49,7 @@ export class AuthService { accessToken, refreshToken, }; - Cookies.set(this.key, JSON.stringify(newUser)); + localStorage.setItem(this.key, JSON.stringify(newUser)); this.setUser(newUser as IAuthStore); } @@ -65,7 +63,7 @@ export class AuthService { ...this.user, [type === 'access' ? 'accessToken' : 'refreshToken']: token, }; - Cookies.set(this.key, JSON.stringify(newUser)); + localStorage.setItem(this.key, JSON.stringify(newUser)); this.setUser(newUser as IAuthStore); } @@ -96,12 +94,12 @@ export class AuthService { * @param data - ๋กœ๊ทธ์ธํ•  ์‚ฌ์šฉ์ž ์ •๋ณด. */ login(data: IAuthStore) { - Cookies.set(this.key, JSON.stringify(data)); + localStorage.setItem(this.key, JSON.stringify(data)); this.setUser(data); } logout() { - Cookies.remove(this.key); + localStorage.removeItem(this.key); this.user = null; this.notifyStateChange(null); } @@ -111,7 +109,7 @@ export class AuthService { * @returns ๋ฉ”์„œ๋“œ ์ฒด์ด๋‹์„ ์œ„ํ•ด ํ˜„์žฌ ์ธ์Šคํ„ด์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. */ initializeUserFromCookie() { - const signedInUser = Cookies.get(this.key); + const signedInUser = localStorage.getItem(this.key); if (signedInUser) { this.setUser(JSON.parse(signedInUser)); diff --git a/services/ahhachul.com/src/utils/common.ts b/services/ahhachul.com/src/utils/common.ts index 4aca41232..09e44e297 100644 --- a/services/ahhachul.com/src/utils/common.ts +++ b/services/ahhachul.com/src/utils/common.ts @@ -1,10 +1,3 @@ -export const isChangedArray = (a: unknown[] = [], b: unknown[] = []) => - a.length !== b.length || a.some((item, index) => !Object.is(item, b[index])); - -export const isValidObject = (obj: unknown): obj is Record => { - return typeof obj === 'object' && obj !== null && !Array.isArray(obj); -}; - export const parseFileExtOfName = (fileName: string): string => fileName.split('.').at(-1) ?? ''; export const downloadFile = (url: string) => { @@ -24,3 +17,9 @@ export const downloadFile = (url: string) => { export const getUserAgent = () => { return window.navigator.userAgent; }; + +/** + * Math.random()์€ 0 ์ด์ƒ 1 ๋ฏธ๋งŒ์˜ ๋ถ€๋™์†Œ์ˆ˜์  ๋‚œ์ˆ˜๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค + * ์ด ๊ฐ’์— 60์„ ๊ณฑํ•˜๊ณ  1์„ ๋”ํ•œ ํ›„ ์†Œ์ˆ˜์ ์„ ๋ฒ„๋ฆฌ๋ฉด 1~60 ์‚ฌ์ด์˜ ์ •์ˆ˜๊ฐ€ ๋ฉ๋‹ˆ๋‹ค + */ +export const getRandomNumber1to60 = () => Math.floor(Math.random() * 60) + 1; diff --git a/services/ahhachul.com/src/utils/format.ts b/services/ahhachul.com/src/utils/format.ts index 60e040d40..41d5384d6 100644 --- a/services/ahhachul.com/src/utils/format.ts +++ b/services/ahhachul.com/src/utils/format.ts @@ -1,38 +1,20 @@ -import { SubwayLineFilterOptions } from '@/types'; - -import { isValidObject } from './common'; +import { + type Stations, + type SubwayLineServerModel, + type UserStationList, + SubwayLineFilterOptions, +} from '@/types'; export const formatSubwayFilterOption = ( lineFilter: SubwayLineFilterOptions, - favoriteLine: number, + favoriteLines?: string, ) => { switch (lineFilter) { case SubwayLineFilterOptions.ALL_LINES: return 0; case SubwayLineFilterOptions.ONLY_MY_LINE: - return favoriteLine; - } -}; - -export const deleteObjectKeyWithEmptyValue = >( - obj: T, - options: { removeEmptyStrings?: boolean; removeZero?: boolean } = {}, -): Partial => { - if (!isValidObject(obj)) { - throw new Error('obj must be a non-null object'); + return favoriteLines; } - - return Object.entries(obj).reduce((result, [key, value]) => { - if ( - value !== undefined && - value !== null && - (options.removeZero ? value !== 0 : true) && - (options.removeEmptyStrings ? value !== '' : true) - ) { - result[key as keyof T] = value; - } - return result; - }, {} as Partial); }; export const formatLost112Content = (content: string) => { @@ -42,3 +24,49 @@ export const formatLost112Content = (content: string) => { return formattedText; }; + +export const formatSubwayLineInfo = (subwayResponse: SubwayLineServerModel) => { + const possibleDuplicatedStations = subwayResponse?.subwayLines.reduce((acc, curr) => { + curr?.stations?.forEach(station => { + if (!acc[station?.name]) { + acc[station?.name] = [ + { + stationId: station?.id, + parentLineId: curr?.id, + parentLineNames: curr?.name, + }, + ]; + } else { + acc[station?.name] = [ + ...acc[station?.name], + { + stationId: station?.id, + parentLineId: curr?.id, + parentLineNames: curr?.name, + }, + ]; + } + }); + return acc; + }, {} as Stations); + return possibleDuplicatedStations; +}; + +export const getFirstParentLineId = (stations: UserStationList): string => { + return stations[0]?.subwayLineInfoList?.map(station => station.subwayLineId)?.join(','); +}; + +export const formatTime = (seconds: number) => { + const minutes = Math.floor(seconds / 60); + const remainingSeconds = seconds % 60; + + if (minutes <= 1.5) { + return '์ง„์ž…'; + } + + if (remainingSeconds === 0) { + return `${minutes}๋ถ„`; + } + + return `${minutes}๋ถ„ ${remainingSeconds}์ดˆ`; +}; diff --git a/services/ahhachul.com/src/utils/index.ts b/services/ahhachul.com/src/utils/index.ts index c53973888..4b23bfa24 100644 --- a/services/ahhachul.com/src/utils/index.ts +++ b/services/ahhachul.com/src/utils/index.ts @@ -1,4 +1,3 @@ -export * from './form'; export * from './common'; export * from './format'; export * from './pagination'; diff --git a/services/ahhachul.com/src/utils/lexical.ts b/services/ahhachul.com/src/utils/lexical.ts index f2c9d2865..574174108 100644 --- a/services/ahhachul.com/src/utils/lexical.ts +++ b/services/ahhachul.com/src/utils/lexical.ts @@ -66,3 +66,46 @@ export const isLexicalContent = (content: unknown): content is LexicalNode => { return false; } }; + +type LexicalTextNode = { + text?: string; + children?: LexicalTextNode[]; + [key: string]: any; +}; + +export const extractTextFromLexical = (state: string, baseText: string): string => { + try { + const parsedData = JSON.parse(state); + + if (typeof parsedData === 'string') { + return parsedData; + } + + // Lexical ์ƒํƒœ์ธ ๊ฒฝ์šฐ + if (parsedData?.root) { + const texts: string[] = []; + + const getTexts = (node: LexicalTextNode) => { + if (node.text) { + texts.push(node.text); + } + + if (node.children) { + node.children.forEach(getTexts); + } + }; + + getTexts(parsedData.root); + return texts.join('\n'); + } + + return baseText; + } catch (error) { + if (typeof state === 'string') { + return state; + } + + console.error('Error extracting text from state:', error); + return baseText; + } +}; diff --git a/services/ahhachul.com/src/utils/localStorage.ts b/services/ahhachul.com/src/utils/localStorage.ts new file mode 100644 index 000000000..d26057ee0 --- /dev/null +++ b/services/ahhachul.com/src/utils/localStorage.ts @@ -0,0 +1,4 @@ +export const getAccessTokenInLocalStorage = () => { + const tokenStore = localStorage.getItem(import.meta.env.VITE_CRYPTO_SECRET_KEY); + return tokenStore ? JSON.parse(tokenStore).accessToken : null; +}; diff --git a/services/ahhachul.com/src/utils/share.ts b/services/ahhachul.com/src/utils/share.ts index 8ae571be6..260cf6f9d 100644 --- a/services/ahhachul.com/src/utils/share.ts +++ b/services/ahhachul.com/src/utils/share.ts @@ -8,7 +8,7 @@ export const getSharePageURL = { const urlMap: ShareableURLMap = { CommunityDetailPage: `${WEB_SERVICE_URL}/community`, - LostFoundDetailPage: `${WEB_SERVICE_URL}/lostFound`, + LostFoundDetailPage: `${WEB_SERVICE_URL}/lost-found`, ComplaintDetailPage: `${WEB_SERVICE_URL}/complaint`, } as const; diff --git a/services/ahhachul.com/src/utils/text.tsx b/services/ahhachul.com/src/utils/text.tsx new file mode 100644 index 000000000..de3ed1245 --- /dev/null +++ b/services/ahhachul.com/src/utils/text.tsx @@ -0,0 +1,27 @@ +import { css } from '@emotion/react'; + +export const applyHighlight = (searchValue: string, suggestion: string) => { + const index = suggestion.indexOf(searchValue); + if (index !== -1) { + const start = suggestion.slice(0, index); + const match = suggestion.slice(index, index + searchValue.length); + const end = suggestion.slice(index + searchValue.length); + + return ( + <> + {start} + + {match} + + {end} + + ); + } + + return suggestion; +}; diff --git a/services/ahhachul.com/src/vite-env.d.ts b/services/ahhachul.com/src/vite-env.d.ts index 1d9d4f8c7..eb729a6d4 100644 --- a/services/ahhachul.com/src/vite-env.d.ts +++ b/services/ahhachul.com/src/vite-env.d.ts @@ -5,6 +5,7 @@ interface ImportMetaEnv { readonly VITE_BASE_URL: string; readonly VITE_APP_NAME: string; + readonly VITE_CRYPTO_SECRET_KEY: string; readonly VITE_WEB_STAGING_URL: 'https://dev.ahhachul.com'; readonly VITE_WEB_PRODUCTION_URL: 'https://ahhachul.com'; readonly VITE_APP_ENV: 'development' | 'staging' | 'production'; diff --git a/services/ahhachul.com/vite.config.ts b/services/ahhachul.com/vite.config.ts index c1c03f985..abecf36f2 100644 --- a/services/ahhachul.com/vite.config.ts +++ b/services/ahhachul.com/vite.config.ts @@ -1,11 +1,61 @@ import react from '@vitejs/plugin-react-swc'; import * as path from 'node:path'; +import { visualizer } from 'rollup-plugin-visualizer'; import { defineConfig } from 'vite'; +import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'; import svgr from 'vite-plugin-svgr'; -// https://vitejs.dev/config/ export default defineConfig({ - plugins: [react({ jsxImportSource: '@emotion/react' }), svgr()], + plugins: [ + react({ jsxImportSource: '@emotion/react' }), + visualizer({ + filename: 'stats.html', + gzipSize: true, + brotliSize: true, + }), + svgr({ + svgrOptions: { + plugins: ['@svgr/plugin-svgo', '@svgr/plugin-jsx'], + svgoConfig: { + multipass: true, + plugins: [ + { + name: 'preset-default', + params: { + overrides: { + cleanupNumericValues: false, + removeViewBox: false, + removeUselessStrokeAndFill: false, + cleanupIds: false, + convertPathData: false, + }, + }, + }, + 'sortAttrs', + 'removeXMLProcInst', + 'removeXMLNS', + 'minifyStyles', + 'removeComments', + 'removeHiddenElems', + 'removeEmptyAttrs', + 'removeEmptyText', + 'removeEmptyContainers', + 'collapseGroups', + 'removeMetadata', + { + name: 'addAttributesToSVGElement', + params: { + attributes: [{ xmlns: 'http://www.w3.org/2000/svg' }], + }, + }, + ], + }, + }, + }), + ViteImageOptimizer({ + test: /\.(jpe?g|png|gif|webp)$/i, + }), + ], server: { port: 3000, }, diff --git a/services/one-app/custom.d.ts b/services/one-app/custom.d.ts deleted file mode 100644 index 6ecb272bc..000000000 --- a/services/one-app/custom.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -declare module '*.svg' { - import React from 'react'; - const svg: React.FC>; - export default svg; -} diff --git a/services/one-app/next.config.mjs b/services/one-app/next.config.mjs deleted file mode 100644 index da8298a45..000000000 --- a/services/one-app/next.config.mjs +++ /dev/null @@ -1,23 +0,0 @@ -/** @type {import('next').NextConfig} */ -const nextConfig = { - reactStrictMode: true, - webpack(config, options) { - // SVG ํŒŒ์ผ์„ @svgr/webpack์œผ๋กœ ์ฒ˜๋ฆฌ - config.module.rules.push({ - test: /\.svg$/, - use: [ - { - loader: '@svgr/webpack', - options: { - svgo: true, - typescript: true, - }, - }, - ], - }); - - return config; - }, -}; - -export default nextConfig; diff --git a/services/one-app/next.config.ts b/services/one-app/next.config.ts new file mode 100644 index 000000000..2e6b21df6 --- /dev/null +++ b/services/one-app/next.config.ts @@ -0,0 +1,44 @@ +const withBundleAnalyzer = require('@next/bundle-analyzer')({ + enabled: process.env.ANALYZE === 'true', +}); + +/** @type {import('next').NextConfig} */ +const nextConfig = { + experimental: { + serverActions: { + bodySizeLimit: '10mb', + }, + turbo: { + rules: { + '*.svg': { + loaders: ['@svgr/webpack'], + as: '*.js', + }, + }, + }, + }, + images: { + loader: 'default', + formats: ['image/avif', 'image/webp'], + imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], + deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840], + }, + webpack(config: any) { + config.module.rules.push({ + test: /\.svg$/, + use: [ + { + loader: '@svgr/webpack', + options: { + svgo: true, + typescript: true, + }, + }, + ], + }); + + return config; + }, +}; + +module.exports = withBundleAnalyzer(nextConfig); diff --git a/services/one-app/package.json b/services/one-app/package.json index 60f08110c..275a700ee 100644 --- a/services/one-app/package.json +++ b/services/one-app/package.json @@ -1,13 +1,12 @@ { "name": "@ahhachul/one-app", - "version": "0.1.0", + "version": "0.0.1", "private": true, "scripts": { - "dev": "next dev", + "dev": "next dev --turbopack", "build": "next build", "start": "next start", - "dev:mocking": "NEXT_PUBLIC_API_MOCKING=enabled next dev", - "server:mocking": "npx tsx watch ./src/mocks/http.ts", + "type-check": "tsc --noEmit", "lint:es": "eslint src", "lint:es:fix": "eslint src --fix", "lint:etc": "prettier src --check", @@ -16,7 +15,11 @@ "lint:fix": "pnpm lint:es:fix && pnpm lint:etc:fix", "test": "jest", "test:watch": "jest --watch", - "test:coverage": "jest --coverage" + "test:coverage": "jest --coverage", + "analyze": "ANALYZE=true next build", + "test:perf": "node scripts/performance-test.mjs", + "test:perf:load": "autocannon http://localhost:3000", + "test:perf:lighthouse": "lighthouse http://localhost:3000 --view" }, "dependencies": { "@lexical/react": "^0.21.0", @@ -24,58 +27,56 @@ "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-slot": "^1.1.1", - "@tanstack/react-query": "^5.59.16", - "axios": "^1.7.7", + "@tanstack/react-query": "^5.62.8", "date-fns": "^4.1.0", "embla-carousel-react": "^8.5.1", "js-cookie": "^3.0.5", "lexical": "^0.21.0", "lucide-react": "^0.460.0", - "next": "15.1.4", - "nuqs": "^2.2.2", - "query-string": "^9.1.1", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "react-intersection-observer": "^9.13.1", + "next": "15.1.6", + "nextjs-toploader": "^3.7.15", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-hook-form": "^7.49.3", + "react-intersection-observer": "^9.15.0", "react-lazy-load-image-component": "^1.6.3", "react-loading-skeleton": "^3.5.0", + "react-swipeable": "^7.0.2", "react-zoom-pan-pinch": "3.6.0", "swiper": "^11.1.15", "vaul": "^1.1.1", "zod": "^3.23.8", - "zustand": "^5.0.0" + "zustand": "^5.0.2" }, "devDependencies": { + "@ahhachul/utils": "workspace:*", "@faker-js/faker": "^9.0.3", - "@mswjs/http-middleware": "^0.10.2", + "@next/bundle-analyzer": "^15.1.6", "@svgr/webpack": "^8.1.0", "@tanstack/react-query-devtools": "^5.59.16", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.2", "@testing-library/react": "^16.0.1", "@testing-library/user-event": "^14.5.2", - "@types/cors": "^2.8.17", - "@types/express": "^5.0.0", "@types/jest": "^29.5.14", "@types/js-cookie": "^3.0.6", - "@types/node": "20.13.0", - "@types/react": "^18.3.12", - "@types/react-dom": "^18.3.1", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", "@types/react-lazy-load-image-component": "^1.6.4", + "autocannon": "^8.0.0", "autoprefixer": "^10.0.1", - "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", - "cors": "^2.8.5", - "express": "^4.21.1", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", + "lighthouse": "^12.3.0", "msw": "^2.5.2", + "puppeteer": "^24.1.1", "postcss": "^8", "tailwind-merge": "^2.5.4", "tailwindcss": "^3.3.0", - "tailwindcss-animate": "^1.0.7", "ts-jest": "^29.1.1", - "typescript": "5.4.5" + "typescript": "^5" }, "msw": { "workerDirectory": [ diff --git a/services/one-app/scripts/performance-test.mjs b/services/one-app/scripts/performance-test.mjs new file mode 100644 index 000000000..97b7944ab --- /dev/null +++ b/services/one-app/scripts/performance-test.mjs @@ -0,0 +1,31 @@ +import autocannon from 'autocannon'; +import lighthouse from 'lighthouse'; +import puppeteer from 'puppeteer'; + +async function runPerformanceTests() { + // Lighthouse ํ…Œ์ŠคํŠธ + const browser = await puppeteer.launch({ headless: true }); + const results = await lighthouse('http://localhost:3000', { + port: new URL(browser.wsEndpoint()).port, + output: 'html', + logLevel: 'info', + }); + + console.log('Lighthouse scores:', results.lhr.categories.performance.score * 100); + + // ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ + const loadTestResults = await autocannon({ + url: 'http://localhost:3000', + connections: 10, + duration: 10, + }); + + console.log('Load test results:', { + 'Avg Latency': loadTestResults.latency.average, + 'Req/Sec': loadTestResults.requests.average, + }); + + await browser.close(); +} + +runPerformanceTests().catch(console.error); diff --git a/services/one-app/src/__test__/1__StartPage.spec.tsx b/services/one-app/src/__test__/1__StartPage.spec.tsx index 03d160313..03356868c 100644 --- a/services/one-app/src/__test__/1__StartPage.spec.tsx +++ b/services/one-app/src/__test__/1__StartPage.spec.tsx @@ -1,8 +1,17 @@ import { render, screen } from '@testing-library/react'; -import Page from '@/app/(site)/page'; +import Home from '@/app/page'; -it.skip('App Router: Works with Server Components', () => { - render(); - expect(screen.getByRole('heading', { name: /App Router/i })).toHaveTextContent('App Router'); +describe('Home Page', () => { + it('renders welcome message', () => { + render(); + expect(screen.getByRole('main')).toBeInTheDocument(); + expect(screen.getByText('์•„ํ•˜์ฒ ๋‹˜,')).toBeInTheDocument(); + }); + + it('has correct styling', () => { + render(); + const main = screen.getByRole('main'); + expect(main).toHaveClass('flex', 'min-h-screen', 'flex-col', 'text-black', 'bg-white', 'pt-4'); + }); }); diff --git a/services/one-app/src/app/(auth)/@modal/(.)i/flow/login/page.tsx b/services/one-app/src/app/(auth)/@modal/(.)i/flow/login/page.tsx new file mode 100644 index 000000000..e48bc68f8 --- /dev/null +++ b/services/one-app/src/app/(auth)/@modal/(.)i/flow/login/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return
    ; +} diff --git a/services/one-app/src/app/(auth)/@modal/default.tsx b/services/one-app/src/app/(auth)/@modal/default.tsx new file mode 100644 index 000000000..6ddf1b76f --- /dev/null +++ b/services/one-app/src/app/(auth)/@modal/default.tsx @@ -0,0 +1,3 @@ +export default function Default() { + return null; +} diff --git a/services/one-app/src/app/(auth)/i/flow/login/page.tsx b/services/one-app/src/app/(auth)/i/flow/login/page.tsx new file mode 100644 index 000000000..e48bc68f8 --- /dev/null +++ b/services/one-app/src/app/(auth)/i/flow/login/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return
    ; +} diff --git a/services/one-app/src/app/(site)/login/_component/HelloOnLogin.tsx b/services/one-app/src/app/(auth)/login/_component/HelloOnLogin.tsx similarity index 85% rename from services/one-app/src/app/(site)/login/_component/HelloOnLogin.tsx rename to services/one-app/src/app/(auth)/login/_component/HelloOnLogin.tsx index 128207ab1..e73e4d2e0 100644 --- a/services/one-app/src/app/(site)/login/_component/HelloOnLogin.tsx +++ b/services/one-app/src/app/(auth)/login/_component/HelloOnLogin.tsx @@ -1,4 +1,4 @@ -import { LogoIcon, LogoTextIcon } from '@/common/assets/icons'; +import { LogoIcon, LogoTextIcon } from '@/asset/icon'; export const HelloOnLogin: React.FC = () => { return ( diff --git a/services/one-app/src/app/(site)/login/_component/SocialLoginButton.tsx b/services/one-app/src/app/(auth)/login/_component/SocialLoginButton.tsx similarity index 73% rename from services/one-app/src/app/(site)/login/_component/SocialLoginButton.tsx rename to services/one-app/src/app/(auth)/login/_component/SocialLoginButton.tsx index 9eebc6463..dd6846502 100644 --- a/services/one-app/src/app/(site)/login/_component/SocialLoginButton.tsx +++ b/services/one-app/src/app/(auth)/login/_component/SocialLoginButton.tsx @@ -1,19 +1,21 @@ +'use client'; + import React from 'react'; -import type { SocialLoginOption } from '@/model'; +import type { SocialLoginOption } from '@/types'; interface SocialLoginButtonProps extends SocialLoginOption { - onLogin: () => Promise; + onLoginAction: () => Promise; } export const SocialLoginButton: React.FC = ({ social, bgColor, icon: Icon, - onLogin, + onLoginAction, }) => ( +//

    ํšŒ์›๊ฐ€์ž…

    +//
    +//

    +// ๋‹‰๋„ค์ž„์„ ์„ค์ •ํ•ด์ฃผ์„ธ์š” +//

    +//
    +//
    +// +//
    +// {isValidateOk ? : isValidateError ? : null} +//
    +//
    +//
    +// +// {nickNameStatusMessage} +// +// {lengthIndicator} +//
    +//
    +// +// +// ); +// }; +// export default SetNickNamePage; + +export default function SetNickNamePage() { + return ( +
    +
    +
    + ); +} diff --git a/services/one-app/src/app/(main-service)/community/[id]/_components/CommunityDetail.tsx b/services/one-app/src/app/(main-service)/community/[id]/_components/CommunityDetail.tsx new file mode 100644 index 000000000..af76da8e7 --- /dev/null +++ b/services/one-app/src/app/(main-service)/community/[id]/_components/CommunityDetail.tsx @@ -0,0 +1,79 @@ +'use client'; + +import { useQuery } from '@tanstack/react-query'; + +import { formatDateTime } from '@ahhachul/utils'; + +import { ReadonlyEditor } from '@/component/Editor'; +// import { SUBWAY_LOGO_SVG_LIST } from '@/component'; +import { TIMESTAMP } from '@/constant'; +import { cn, isLexicalContent } from '@/util'; + +import { CommunityTypeBadge } from './CommunityTypeBadge'; + +import { getCommunityDetailPost } from '../_lib/getDetailPost'; + +type Props = { + id: number; +}; + +export default function CommunityPostDetail({ id }: Props) { + const { data: post } = useQuery({ + queryKey: ['community-post', id], + queryFn: getCommunityDetailPost, + staleTime: 5 * TIMESTAMP.MINUTE, + select: res => res.result, + }); + + if (!post) return null; + + // const images = post.isFromLost112 + // ? [ + // { + // imageId: getRandomInt(), + // imageUrl: post.externalSourceImageUrl, + // }, + // ] + // : post.images; + + return ( + <> +
    + {/* */} +
    + +
    + {post.title} +
    +
    +
    + {post.writer || '๋กœ์ŠคํŠธ 112'} + {formatDateTime(post.createdAt!)} +
    +
    + {/* {SUBWAY_LOGO_SVG_LIST[post.subwayLineId]} */} +
    +
    +
    + +
    + {isLexicalContent(post.content) ? ( + div>div]:p-0', '[&>div>div]:border-none')} + /> + ) : ( +

    {post.content}

    + )} +
    +
    + + {/* */} + {/* */} + + ); +} diff --git a/services/one-app/src/app/(main-service)/community/[id]/_components/CommunityTypeBadge.tsx b/services/one-app/src/app/(main-service)/community/[id]/_components/CommunityTypeBadge.tsx new file mode 100644 index 000000000..4dd05bfd4 --- /dev/null +++ b/services/one-app/src/app/(main-service)/community/[id]/_components/CommunityTypeBadge.tsx @@ -0,0 +1,19 @@ +'use client'; + +import { CommunityType } from '@/types/community'; + +interface Props { + communityType: CommunityType; +} + +export const CommunityTypeBadge = ({ communityType }: Props) => { + return ( +
    + {communityType === CommunityType.FREE + ? '์ž์œ ' + : communityType === CommunityType.HUMOR + ? '์œ ๋จธ' + : '์ •๋ณด'} +
    + ); +}; diff --git a/services/one-app/src/app/(main-service)/community/[id]/_components/Lost112ArticleTable.tsx b/services/one-app/src/app/(main-service)/community/[id]/_components/Lost112ArticleTable.tsx new file mode 100644 index 000000000..92caeae66 --- /dev/null +++ b/services/one-app/src/app/(main-service)/community/[id]/_components/Lost112ArticleTable.tsx @@ -0,0 +1,77 @@ +'use client'; + +import Link from 'next/link'; + +import { formatDateTime } from '@ahhachul/utils'; + +import type { LostFoundPostDetail } from '@/types'; + +interface Props { + post: LostFoundPostDetail; +} + +export const Lost112ArticleTable = ({ post }: Props) => { + return ( +
    +
    +

    + ์œ ์‹ค๋ฌผ ์ƒ์„ธ์ •๋ณด +

    + +
    +
    ์Šต๋“์ผ
    +
    {formatDateTime(post.createdAt)}
    + + {post?.storage && ( + <> +
    ์Šต๋“์žฅ์†Œ
    +
    {post.storage}
    + + )} + + {post?.categoryName && ( + <> +
    ๋ฌผํ’ˆ๋ถ„๋ฅ˜
    +
    {post.categoryName}
    + + )} + + {post?.storageNumber && ( + <> +
    ๋ณด๊ด€ ์žฅ์†Œ ์ „ํ™”๋ฒˆํ˜ธ
    +
    {post.storageNumber}
    + + )} + + {post?.storage && ( + <> +
    ๋ณด๊ด€์žฅ์†Œ
    +
    {post.storage}
    + + )} + + {post?.pageUrl && ( + <> +
    ์›๋ณธ ๊ฒŒ์‹œ๊ธ€
    + + ๋ฐ”๋กœ๊ฐ€๊ธฐ + + + )} +
    + +
    +
    + + {post.status === 'PROGRESS' ? 'ํ˜„์žฌ ๋ณด๊ด€์ค‘ ์ž…๋‹ˆ๋‹ค.' : '์ฐพ๊ธฐ ์™„๋ฃŒ!'} + +
    +
    +
    + ); +}; diff --git a/services/one-app/src/app/(site)/lost-found/_components/postDetail/RecommendArticles.tsx b/services/one-app/src/app/(main-service)/community/[id]/_components/RecommendArticles.tsx similarity index 65% rename from services/one-app/src/app/(site)/lost-found/_components/postDetail/RecommendArticles.tsx rename to services/one-app/src/app/(main-service)/community/[id]/_components/RecommendArticles.tsx index 318f4505d..e017aca79 100644 --- a/services/one-app/src/app/(site)/lost-found/_components/postDetail/RecommendArticles.tsx +++ b/services/one-app/src/app/(main-service)/community/[id]/_components/RecommendArticles.tsx @@ -1,13 +1,15 @@ -import { ChevronIcon } from '@/common/assets/icons'; -import { RecommendArticleCard } from '@/common/components'; -import type { RecommendPost } from '@/model'; +'use client'; + +import { ChevronIcon } from '@/asset/icon'; +import { RecommendArticleCard } from '@/component'; +import { IRecommendPost } from '@/types'; interface Props { - posts: RecommendPost[]; + posts: IRecommendPost[]; } export const RecommendArticles = ({ posts }: Props) => { - if (!posts.length) return null; + if (!posts?.length) return null; return (
    diff --git a/services/one-app/src/app/(main-service)/community/[id]/_lib/getDetailPost.ts b/services/one-app/src/app/(main-service)/community/[id]/_lib/getDetailPost.ts new file mode 100644 index 000000000..37f648395 --- /dev/null +++ b/services/one-app/src/app/(main-service)/community/[id]/_lib/getDetailPost.ts @@ -0,0 +1,25 @@ +import { QueryFunction } from '@tanstack/react-query'; + +import type { IResponse } from '@/types'; +import type { CommunityDetail } from '@/types/community'; + +export const getCommunityDetailPost: QueryFunction< + IResponse, + [_1: string, id: number] +> = async ({ queryKey }) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [_1, id] = queryKey; + const res = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/community-posts/${id}`, { + next: { + tags: ['lcommunity-post', id.toString()], + }, + credentials: 'include', + }); + + if (!res.ok) { + // This will activate the closest `error.js` Error Boundary + throw new Error('Failed to fetch data'); + } + + return res.json(); +}; diff --git a/services/one-app/src/app/(main-service)/community/[id]/_lib/getDetailPostServer.ts b/services/one-app/src/app/(main-service)/community/[id]/_lib/getDetailPostServer.ts new file mode 100644 index 000000000..a2bf0ad92 --- /dev/null +++ b/services/one-app/src/app/(main-service)/community/[id]/_lib/getDetailPostServer.ts @@ -0,0 +1,25 @@ +import { cookies } from 'next/headers'; + +export const getCommunityDetailPostServer = async ({ + queryKey, +}: { + queryKey: [string, number]; +}) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [_1, id] = queryKey; + const res = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/community-posts/${id}`, { + next: { + revalidate: 3600, + tags: ['community-post', id.toString()], + }, + credentials: 'include', + headers: { Cookie: (await cookies()).toString() }, + }); + + if (!res.ok) { + // This will activate the closest `error.js` Error Boundary + throw new Error('Failed to fetch data'); + } + + return res.json(); +}; diff --git a/services/one-app/src/app/(main-service)/community/[id]/page.tsx b/services/one-app/src/app/(main-service)/community/[id]/page.tsx new file mode 100644 index 000000000..c4167133c --- /dev/null +++ b/services/one-app/src/app/(main-service)/community/[id]/page.tsx @@ -0,0 +1,73 @@ +import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query'; +import type { Metadata } from 'next'; + +import { SUBWAY_LINES } from '@/constant'; +import { extractTextFromLexical } from '@/util'; + +import CommunityPostDetail from './_components/CommunityDetail'; +import { getCommunityDetailPostServer } from './_lib/getDetailPostServer'; + +export async function generateMetadata({ params }: Props): Promise { + const { id } = await params; + const post = await getCommunityDetailPostServer({ queryKey: ['community-post', id] }); + + const subwayLineId = post.result.subwayLineId; + + const baseTitle = (subwayLineId?: string) => + `${ + post.result.title.length > 16 ? post.result.title.slice(0, 16) + '...' : post.result.title + } / ${subwayLineId} ์ปค๋ฎค๋‹ˆํ‹ฐ - ์•„ํ•˜์ฒ `; + + const title = + subwayLineId && +subwayLineId !== 0 + ? baseTitle(SUBWAY_LINES.find(subway => subway.id === +subwayLineId)?.name) + : baseTitle('์ง€ํ•˜์ฒ '); + + const baseDescription = + '์ง€ํ•˜์ฒ  ์ด์šฉ๊ฐ๋“ค๊ณผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์†Œํ†ตํ•˜์„ธ์š”. ์ง€ํ•˜์ฒ  ๊ด€๋ จ ์ •๋ณด, ๊ฟ€ํŒ, ์ผ์ƒ ์ด์•ผ๊ธฐ๋ถ€ํ„ฐ ์ง€ํ•˜์ฒ  ์šดํ–‰ ์ƒํ™ฉ๊นŒ์ง€ ๋‹ค์–‘ํ•œ ์ด์•ผ๊ธฐ๋ฅผ ๋‚˜๋ˆŒ ์ˆ˜ ์žˆ๋Š” ๊ณต๊ฐ„์ž…๋‹ˆ๋‹ค. ํ•จ๊ป˜ ๋งŒ๋“ค์–ด๊ฐ€๋Š” ์ง€ํ•˜์ฒ  ์ปค๋ฎค๋‹ˆํ‹ฐ, ์•„ํ•˜์ฒ ์—์„œ ์‹œ์ž‘ํ•˜์„ธ์š”.'; + + const image = + subwayLineId && +subwayLineId !== 0 + ? `https://static.dev.ahhachul.com/banners/community/subway-line-${subwayLineId}.png` + : 'https://static.dev.ahhachul.com/banners/community/main.png'; + + return { + title, + description: extractTextFromLexical(post.result.content, baseDescription), + openGraph: { + title, + description: extractTextFromLexical(post.result.content, baseDescription), + images: [ + { + url: image, + width: 800, + height: 400, + }, + ], + }, + }; +} + +type Props = { + params: Promise<{ + id: number; + }>; +}; + +export default async function CommunityDetailPage(props: Props) { + const { id } = await props.params; + const queryClient = new QueryClient(); + await queryClient.prefetchQuery({ + queryKey: ['community-post', id], + queryFn: getCommunityDetailPostServer, + }); + const dehydratedState = dehydrate(queryClient); + + return ( +
    + + + +
    + ); +} diff --git a/services/one-app/src/app/(main-service)/community/_components/CommunityPosts.tsx b/services/one-app/src/app/(main-service)/community/_components/CommunityPosts.tsx new file mode 100644 index 000000000..ac7975b7b --- /dev/null +++ b/services/one-app/src/app/(main-service)/community/_components/CommunityPosts.tsx @@ -0,0 +1,61 @@ +'use client'; + +import { Fragment, useEffect } from 'react'; +import { useInView } from 'react-intersection-observer'; + +import { InfiniteData, useInfiniteQuery } from '@tanstack/react-query'; +import Link from 'next/link'; +import { useSearchParams } from 'next/navigation'; + +import { ArticleListSuspenseFallback, Post } from '@/component'; +import type { ApiResponse, PaginatedList } from '@/types'; +import { CommunityPost } from '@/types/community'; + +import { getCommunityPosts } from '../_lib/getCommunityPosts'; + +export default function CommunityPosts() { + const searchParams = useSearchParams(); + + const { data, hasNextPage, fetchNextPage, isFetching, isPending } = useInfiniteQuery< + ApiResponse>, + Error, + InfiniteData>>, + [_1: string, _2: string, _3: string], + string + >({ + queryKey: ['community', 'posts', searchParams.toString()], + queryFn: getCommunityPosts, + initialPageParam: '', + getNextPageParam: lastPage => lastPage.result.pageToken, + staleTime: 60 * 1000, + gcTime: 300 * 1000, + }); + + const { ref, inView } = useInView({ + delay: 500, + threshold: 0, + }); + + useEffect(() => { + if (inView) { + !isFetching && hasNextPage && fetchNextPage(); + } + }, [inView, isFetching, hasNextPage, fetchNextPage]); + + if (isPending) return ; + + return ( + <> + {data?.pages.map((page, i) => ( + + {page.result.data.map(post => ( + + + + ))} + + ))} +
    + + ); +} diff --git a/services/one-app/src/app/(main-service)/community/_components/FilterList.tsx b/services/one-app/src/app/(main-service)/community/_components/FilterList.tsx new file mode 100644 index 000000000..f1c4b9a2b --- /dev/null +++ b/services/one-app/src/app/(main-service)/community/_components/FilterList.tsx @@ -0,0 +1,27 @@ +'use client'; + +import { useSearchParams } from 'next/navigation'; + +import { ResetFilter, DropdownFilter } from '@/component'; +import { subwayLineIdOptions } from '@/constant'; +import { communityTypeOptions, defaultCommunityFilterValues } from '@/constant/community'; +import { SubwayLineFilterOptions } from '@/types'; +import { CommunityType } from '@/types/community'; + +const Filters = () => { + const searchParams = useSearchParams(); + const category = (searchParams.get('category') as CommunityType) ?? CommunityType.HOT; + const subwayLineId = + (searchParams.get('subwayLineId') as SubwayLineFilterOptions) ?? + SubwayLineFilterOptions.ALL_LINES; + + return ( +
    + + + +
    + ); +}; + +export default Filters; diff --git a/services/one-app/src/app/(main-service)/community/_lib/getCommunityPosts.tsx b/services/one-app/src/app/(main-service)/community/_lib/getCommunityPosts.tsx new file mode 100644 index 000000000..a9fcf0b61 --- /dev/null +++ b/services/one-app/src/app/(main-service)/community/_lib/getCommunityPosts.tsx @@ -0,0 +1,46 @@ +import { objectToQueryString, removeFalsyValues } from '@ahhachul/utils'; + +import { fetchClient } from '@/lib/fetch-client'; +import { type ApiResponse, type PaginatedList } from '@/types'; +import { type CommunityListParams, type CommunityPost, CommunityType } from '@/types/community'; + +type Props = { + pageParam?: string; + queryKey: [_1: string, _2: string, filters: string]; +}; + +export async function getCommunityPosts({ + pageParam, + queryKey, +}: Props): Promise>> { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [_1, _2, query] = queryKey; + + const filters = new URLSearchParams(query); + + const endpoint = + filters.has('category') && filters.get('category') !== CommunityType.HOT + ? 'community-posts' + : 'community-hot-posts'; + + const params = removeFalsyValues({ + ...(filters.get('keyword') && { content: filters.get('keyword') || '' }), + ...(filters.get('subwayLineId') && { subwayLineId: filters.get('subwayLineId') || '' }), + pageSize: 10, + sort: 'createdAt,desc', + ...(pageParam && { pageToken: pageParam }), + ...(filters.has('category') && + filters.get('category') !== CommunityType.HOT && { + categoryType: filters.get('category'), + }), + }) as Partial; + + return await fetchClient( + `${process.env.NEXT_PUBLIC_BASE_URL}/${endpoint}?${objectToQueryString(params)}`, + { + next: { + tags: ['community', 'posts'], + }, + }, + ); +} diff --git a/services/one-app/src/app/(main-service)/community/_lib/metadata.ts b/services/one-app/src/app/(main-service)/community/_lib/metadata.ts new file mode 100644 index 000000000..a7b208fef --- /dev/null +++ b/services/one-app/src/app/(main-service)/community/_lib/metadata.ts @@ -0,0 +1,51 @@ +import type { Metadata } from 'next'; + +import { SUBWAY_LINES } from '@/constant'; +import { CommunityType } from '@/types/community'; + +export async function generateCommunityMetadata( + searchParams: Promise<{ + q?: string; + subwayLineId?: string; + category?: CommunityType; + }>, +): Promise { + const { subwayLineId } = await searchParams; + + const baseTitle = '์ง€ํ•˜์ฒ  ์ปค๋ฎค๋‹ˆํ‹ฐ / 1๋“ฑ ์ง€ํ•˜์ฒ  ๋ฏผ์› & ๋ถ„์‹ค๋ฌผ & ์ปค๋ฎค๋‹ˆํ‹ฐ ์ •๋ณด ์•ฑ - ์•„ํ•˜์ฒ '; + + const baseDescription = + '์ง€ํ•˜์ฒ  ์ด์šฉ๊ฐ๋“ค๊ณผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์†Œํ†ตํ•˜์„ธ์š”. ์ง€ํ•˜์ฒ  ๊ด€๋ จ ์ •๋ณด, ๊ฟ€ํŒ, ์ผ์ƒ ์ด์•ผ๊ธฐ๋ถ€ํ„ฐ ์ง€ํ•˜์ฒ  ์šดํ–‰ ์ƒํ™ฉ๊นŒ์ง€ ๋‹ค์–‘ํ•œ ์ด์•ผ๊ธฐ๋ฅผ ๋‚˜๋ˆŒ ์ˆ˜ ์žˆ๋Š” ๊ณต๊ฐ„์ž…๋‹ˆ๋‹ค. ํ•จ๊ป˜ ๋งŒ๋“ค์–ด๊ฐ€๋Š” ์ง€ํ•˜์ฒ  ์ปค๋ฎค๋‹ˆํ‹ฐ, ์•„ํ•˜์ฒ ์—์„œ ์‹œ์ž‘ํ•˜์„ธ์š”.'; + + const title = + subwayLineId && +subwayLineId !== 0 + ? `${SUBWAY_LINES.find(subway => subway.id === +subwayLineId)?.name} ${baseTitle}` + : baseTitle; + + const description = + subwayLineId && +subwayLineId !== 0 + ? `${SUBWAY_LINES.find(subway => subway.id === +subwayLineId)?.name} ${baseDescription}` + : baseDescription; + + const image = + subwayLineId && +subwayLineId !== 0 + ? `https://static.dev.ahhachul.com/banners/community/subway-line-${subwayLineId}.png` + : 'https://static.dev.ahhachul.com/banners/community/main.png'; + + return { + title, + description, + applicationName: '์•„ํ•˜์ฒ  | AhHachul', + openGraph: { + title, + description, + images: [ + { + url: image, + width: 800, + height: 400, + }, + ], + }, + }; +} diff --git a/services/one-app/src/app/(main-service)/community/_lib/prefetchPosts.ts b/services/one-app/src/app/(main-service)/community/_lib/prefetchPosts.ts new file mode 100644 index 000000000..019e25b17 --- /dev/null +++ b/services/one-app/src/app/(main-service)/community/_lib/prefetchPosts.ts @@ -0,0 +1,28 @@ +import type { QueryClient, InfiniteData } from '@tanstack/react-query'; + +import { objectToQueryString } from '@ahhachul/utils'; + +import type { ApiResponse, PaginatedList, SubwayLineFilterOptions } from '@/types'; +import { CommunityPost, CommunityType } from '@/types/community'; + +import { getCommunityPosts } from './getCommunityPosts'; + +type SearchParams = { + q?: string; + category?: CommunityType; + subwayLineId?: SubwayLineFilterOptions; +}; + +export async function prefetchPosts(queryClient: QueryClient, query: SearchParams) { + await queryClient.prefetchInfiniteQuery< + ApiResponse>, + Error, + InfiniteData>>, + [_1: string, _2: string, _3: string], + string + >({ + queryKey: ['community', 'posts', objectToQueryString(query)], + queryFn: getCommunityPosts, + initialPageParam: '', + }); +} diff --git a/services/one-app/src/app/(main-service)/community/page.tsx b/services/one-app/src/app/(main-service)/community/page.tsx new file mode 100644 index 000000000..444e8196d --- /dev/null +++ b/services/one-app/src/app/(main-service)/community/page.tsx @@ -0,0 +1,47 @@ +import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query'; +import { headers } from 'next/headers'; + +import SearchForm from '@/component/SearchForm'; +import { SubwayLineFilterOptions } from '@/types'; +import { CommunityType } from '@/types/community'; + +import CommunityPosts from './_components/CommunityPosts'; +import Filters from './_components/FilterList'; +import { generateCommunityMetadata } from './_lib/metadata'; +import { prefetchPosts } from './_lib/prefetchPosts'; + +type Props = { + searchParams: Promise<{ + q?: string; + category?: CommunityType; + subwayLineId?: SubwayLineFilterOptions; + }>; +}; + +export async function generateMetadata({ searchParams }: Props) { + return generateCommunityMetadata(searchParams); +} + +export default async function CommunityPage({ searchParams }: Props) { + const query = await searchParams; + const headersList = await headers(); + const isServerRender = !headersList.get('next-url'); + + let dehydratedState; + + if (isServerRender) { + const queryClient = new QueryClient(); + await prefetchPosts(queryClient, query); + dehydratedState = dehydrate(queryClient); + } + + return ( +
    + + + + + +
    + ); +} diff --git a/services/one-app/src/app/(main-service)/complaint/[id]/_components/ComplaintDetail.tsx b/services/one-app/src/app/(main-service)/complaint/[id]/_components/ComplaintDetail.tsx new file mode 100644 index 000000000..46c7fdf5f --- /dev/null +++ b/services/one-app/src/app/(main-service)/complaint/[id]/_components/ComplaintDetail.tsx @@ -0,0 +1,71 @@ +'use client'; + +import { useQuery } from '@tanstack/react-query'; + +import { formatDateTime } from '@ahhachul/utils'; + +import { ReadonlyEditor } from '@/component/Editor'; +import { TIMESTAMP } from '@/constant'; +import { cn, extractTextFromLexical, isLexicalContent } from '@/util'; + +import { ComplaintTypeBadge } from './ComplaintTypeBadge'; + +import { getComplaintDetailPost } from '../_lib/getDetailPost'; + +type Props = { + id: number; +}; + +export default function CommunityPostDetail({ id }: Props) { + const { data: post } = useQuery({ + queryKey: ['complaint-post', id], + queryFn: getComplaintDetailPost, + staleTime: 5 * TIMESTAMP.MINUTE, + select: res => res.result, + }); + + console.log('post.result:', post); + + if (!post) return null; + + const title = extractTextFromLexical(post.content, post.complaintType).slice(0, 20); + + return ( + <> +
    + {/* */} +
    + +
    {title}
    +
    +
    + {post.writer || '๋กœ์ŠคํŠธ 112'} + {formatDateTime(post.createdAt!)} +
    +
    + {/* {SUBWAY_LOGO_SVG_LIST[post.subwayLineId]} */} +
    +
    +
    + +
    + {isLexicalContent(post.content) ? ( + div>div]:p-0', '[&>div>div]:border-none')} + /> + ) : ( +

    {post.content}

    + )} +
    +
    + + {/* */} + {/* */} + + ); +} diff --git a/services/one-app/src/app/(main-service)/complaint/[id]/_components/ComplaintTypeBadge.tsx b/services/one-app/src/app/(main-service)/complaint/[id]/_components/ComplaintTypeBadge.tsx new file mode 100644 index 000000000..17b97af14 --- /dev/null +++ b/services/one-app/src/app/(main-service)/complaint/[id]/_components/ComplaintTypeBadge.tsx @@ -0,0 +1,25 @@ +'use client'; + +import type { ComplaintType } from '@/types/complaint'; + +const complaintTypeOptions: Record = { + ENVIRONMENTAL_COMPLAINT: 'ํ™˜๊ฒฝ๋ฏผ์›', + TEMPERATURE_CONTROL: '์˜จ๋„์กฐ์ ˆ', + DISORDER: '์งˆ์„œ์ €ํ•ด', + ANNOUNCEMENT: '์•ˆ๋‚ด๋ฐฉ์†ก', + EMERGENCY_PATIENT: '์‘๊ธ‰ํ™˜์ž', + VIOLENCE: 'ํญ๋ ฅ', + SEXUAL_HARASSMENT: '์„ฑ์ถ”ํ–‰', +}; + +interface Props { + complaintType: ComplaintType; +} + +export const ComplaintTypeBadge = ({ complaintType }: Props) => { + return ( +
    + {complaintTypeOptions[complaintType]} +
    + ); +}; diff --git a/services/one-app/src/app/(main-service)/complaint/[id]/_lib/getDetailPost.ts b/services/one-app/src/app/(main-service)/complaint/[id]/_lib/getDetailPost.ts new file mode 100644 index 000000000..64fed9db3 --- /dev/null +++ b/services/one-app/src/app/(main-service)/complaint/[id]/_lib/getDetailPost.ts @@ -0,0 +1,25 @@ +import { QueryFunction } from '@tanstack/react-query'; + +import type { IResponse } from '@/types'; +import { ComplaintPostDetail } from '@/types/complaint'; + +export const getComplaintDetailPost: QueryFunction< + IResponse, + [_1: string, id: number] +> = async ({ queryKey }) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [_1, id] = queryKey; + const res = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/complaintcomplaint-posts/${id}`, { + next: { + tags: ['complaint-post', id.toString()], + }, + credentials: 'include', + }); + + if (!res.ok) { + // This will activate the closest `error.js` Error Boundary + throw new Error('Failed to fetch data'); + } + + return res.json(); +}; diff --git a/services/one-app/src/app/(main-service)/complaint/[id]/_lib/getDetailPostServer.ts b/services/one-app/src/app/(main-service)/complaint/[id]/_lib/getDetailPostServer.ts new file mode 100644 index 000000000..3def0b19d --- /dev/null +++ b/services/one-app/src/app/(main-service)/complaint/[id]/_lib/getDetailPostServer.ts @@ -0,0 +1,25 @@ +import { cookies } from 'next/headers'; + +export const getComplaintDetailPostServer = async ({ + queryKey, +}: { + queryKey: [string, number]; +}) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [_1, id] = queryKey; + const res = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/complaint-posts/${id}`, { + next: { + revalidate: 3600, + tags: ['complaint-post', id.toString()], + }, + credentials: 'include', + headers: { Cookie: (await cookies()).toString() }, + }); + + if (!res.ok) { + // This will activate the closest `error.js` Error Boundary + throw new Error('Failed to fetch data'); + } + + return res.json(); +}; diff --git a/services/one-app/src/app/(main-service)/complaint/[id]/page.tsx b/services/one-app/src/app/(main-service)/complaint/[id]/page.tsx new file mode 100644 index 000000000..7de765f22 --- /dev/null +++ b/services/one-app/src/app/(main-service)/complaint/[id]/page.tsx @@ -0,0 +1,76 @@ +import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query'; +import type { Metadata } from 'next'; + +import { SUBWAY_LINES } from '@/constant'; +import { extractTextFromLexical } from '@/util'; + +import ComplaintDetail from './_components/ComplaintDetail'; +import { getComplaintDetailPostServer } from './_lib/getDetailPostServer'; + +export async function generateMetadata({ params }: Props): Promise { + const { id } = await params; + const post = await getComplaintDetailPostServer({ queryKey: ['complaint-post', id] }); + + const subwayLineId = post.result.subwayLineId; + const extractTitle = extractTextFromLexical(post.result.content, post.result.complaintType).slice( + 0, + 16, + ); + const baseTitle = (subwayLineId?: string) => + `${extractTitle} / ${subwayLineId} ๋ฏผ์› ์ ‘์ˆ˜ - ์•„ํ•˜์ฒ `; + + const title = + subwayLineId && +subwayLineId !== 0 + ? baseTitle(SUBWAY_LINES.find(subway => subway.id === +subwayLineId)?.name) + : baseTitle('์ง€ํ•˜์ฒ '); + + const baseDescription = + '์ง€ํ•˜์ฒ  ์ด์šฉ ์ค‘ ๋ถˆํŽธ์‚ฌํ•ญ์„ ์‰ฝ๊ณ  ๋น ๋ฅด๊ฒŒ ์‹ ๊ณ ํ•˜์„ธ์š”. ์‹œ์„ค๋ฌผ ๊ณ ์žฅ, ๋ถˆํŽธ์‚ฌํ•ญ, ๊ฐœ์„  ์š”์ฒญ ๋“ฑ ๋‹ค์–‘ํ•œ ๋ฏผ์›์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ ‘์ˆ˜ํ•˜๊ณ  ์ฒ˜๋ฆฌ ํ˜„ํ™ฉ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋” ๋‚˜์€ ์ง€ํ•˜์ฒ  ํ™˜๊ฒฝ์„ ๋งŒ๋“œ๋Š” ์ฒซ๊ฑธ์Œ, ์•„ํ•˜์ฒ  ๋ฏผ์› ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค.'; + + const image = + post.result.images.length > 0 && post.result.images.at(0).imageUrl + ? post.result.images.at(0).imageUrl + : subwayLineId && +subwayLineId !== 0 + ? `https://static.dev.ahhachul.com/banners/complaint/subway-line-${subwayLineId}.png` + : 'https://static.dev.ahhachul.com/banners/complaint/main.png'; + + return { + title, + description: extractTextFromLexical(post.result.content, baseDescription), + openGraph: { + title, + description: extractTextFromLexical(post.result.content, baseDescription), + images: [ + { + url: image, + width: 800, + height: 400, + }, + ], + }, + }; +} + +type Props = { + params: Promise<{ + id: number; + }>; +}; + +export default async function ComplaintDetailPage(props: Props) { + const { id } = await props.params; + const queryClient = new QueryClient(); + await queryClient.prefetchQuery({ + queryKey: ['complaint-post', id], + queryFn: getComplaintDetailPostServer, + }); + const dehydratedState = dehydrate(queryClient); + + return ( +
    + + + +
    + ); +} diff --git a/services/one-app/src/app/(main-service)/complaint/_lib/metadata.ts b/services/one-app/src/app/(main-service)/complaint/_lib/metadata.ts new file mode 100644 index 000000000..4c9f76375 --- /dev/null +++ b/services/one-app/src/app/(main-service)/complaint/_lib/metadata.ts @@ -0,0 +1,51 @@ +import type { Metadata } from 'next'; + +import { SUBWAY_LINES } from '@/constant'; +import { CommunityType } from '@/types/community'; + +export async function generateComplaintyMetadata( + searchParams: Promise<{ + q?: string; + subwayLineId?: string; + category?: CommunityType; + }>, +): Promise { + const { subwayLineId } = await searchParams; + + const baseTitle = '์ง€ํ•˜์ฒ  ๋ฏผ์› ์ ‘์ˆ˜ / 1๋“ฑ ์ง€ํ•˜์ฒ  ๋ฏผ์› & ๋ถ„์‹ค๋ฌผ & ์ปค๋ฎค๋‹ˆํ‹ฐ ์ •๋ณด ์•ฑ - ์•„ํ•˜์ฒ '; + + const baseDescription = + '์ง€ํ•˜์ฒ  ์ด์šฉ ์ค‘ ๋ถˆํŽธ์‚ฌํ•ญ์„ ์‰ฝ๊ณ  ๋น ๋ฅด๊ฒŒ ์‹ ๊ณ ํ•˜์„ธ์š”. ์‹œ์„ค๋ฌผ ๊ณ ์žฅ, ๋ถˆํŽธ์‚ฌํ•ญ, ๊ฐœ์„  ์š”์ฒญ ๋“ฑ ๋‹ค์–‘ํ•œ ๋ฏผ์›์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ ‘์ˆ˜ํ•˜๊ณ  ์ฒ˜๋ฆฌ ํ˜„ํ™ฉ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋” ๋‚˜์€ ์ง€ํ•˜์ฒ  ํ™˜๊ฒฝ์„ ๋งŒ๋“œ๋Š” ์ฒซ๊ฑธ์Œ, ์•„ํ•˜์ฒ  ๋ฏผ์› ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค.'; + + const title = + subwayLineId && +subwayLineId !== 0 + ? `${SUBWAY_LINES.find(subway => subway.id === +subwayLineId)?.name} ${baseTitle}` + : baseTitle; + + const description = + subwayLineId && +subwayLineId !== 0 + ? `${SUBWAY_LINES.find(subway => subway.id === +subwayLineId)?.name} ${baseDescription}` + : baseDescription; + + const image = + subwayLineId && +subwayLineId !== 0 + ? `https://static.dev.ahhachul.com/banners/complaint/subway-line-${subwayLineId}.png` + : 'https://static.dev.ahhachul.com/banners/complaint/main.png'; + + return { + title, + description, + applicationName: '์•„ํ•˜์ฒ  | AhHachul', + openGraph: { + title, + description, + images: [ + { + url: image, + width: 800, + height: 400, + }, + ], + }, + }; +} diff --git a/services/one-app/src/app/(main-service)/complaint/page.tsx b/services/one-app/src/app/(main-service)/complaint/page.tsx new file mode 100644 index 000000000..473ccda49 --- /dev/null +++ b/services/one-app/src/app/(main-service)/complaint/page.tsx @@ -0,0 +1,18 @@ +import type { SubwayLineFilterOptions } from '@/types'; + +import { generateComplaintyMetadata } from './_lib/metadata'; + +type Props = { + searchParams: Promise<{ + q?: string; + subwayLineId?: SubwayLineFilterOptions; + }>; +}; + +export async function generateMetadata({ searchParams }: Props) { + return generateComplaintyMetadata(searchParams); +} + +export default function ComplaintPage() { + return
    ๋ฏผ์›
    ; +} diff --git a/services/one-app/src/app/(site)/lost-found/_components/postDetail/Lost112ArticleTable.tsx b/services/one-app/src/app/(main-service)/lost-found/[id]/_components/Lost112ArticleTable.tsx similarity index 94% rename from services/one-app/src/app/(site)/lost-found/_components/postDetail/Lost112ArticleTable.tsx rename to services/one-app/src/app/(main-service)/lost-found/[id]/_components/Lost112ArticleTable.tsx index d7f176ca8..ff0337459 100644 --- a/services/one-app/src/app/(site)/lost-found/_components/postDetail/Lost112ArticleTable.tsx +++ b/services/one-app/src/app/(main-service)/lost-found/[id]/_components/Lost112ArticleTable.tsx @@ -1,7 +1,8 @@ import Link from 'next/link'; -import { formatDate } from '@/common/utils'; -import type { LostFoundPostDetail } from '@/model'; +import { formatDateTime } from '@ahhachul/utils'; + +import type { LostFoundPostDetail } from '@/types'; interface Props { post: LostFoundPostDetail; @@ -17,7 +18,7 @@ export const Lost112ArticleTable = ({ post }: Props) => {
    ์Šต๋“์ผ
    -
    {formatDate(post.createdAt)}
    +
    {formatDateTime(post.createdAt)}
    {post?.storage && ( <> diff --git a/services/one-app/src/app/(site)/lost-found/_components/postDetail/PostDetail.tsx b/services/one-app/src/app/(main-service)/lost-found/[id]/_components/LostFoundDetail.tsx similarity index 62% rename from services/one-app/src/app/(site)/lost-found/_components/postDetail/PostDetail.tsx rename to services/one-app/src/app/(main-service)/lost-found/[id]/_components/LostFoundDetail.tsx index cc6e95021..2d09180fe 100644 --- a/services/one-app/src/app/(site)/lost-found/_components/postDetail/PostDetail.tsx +++ b/services/one-app/src/app/(main-service)/lost-found/[id]/_components/LostFoundDetail.tsx @@ -1,41 +1,48 @@ 'use client'; +import { useQuery } from '@tanstack/react-query'; import Image from 'next/image'; -import { LexicalSyntaxContentParser } from '@/app/(site)/_component/Editor'; -import { BaseArticleImages, CommentTextField, SUBWAY_LOGO_SVG_LIST } from '@/common/components'; -import { cn, formatDate, getRandomInt, isLexicalContent } from '@/common/utils'; +import { formatDateTime } from '@ahhachul/utils'; + +import { ReadonlyEditor } from '@/component/Editor'; +// import { SUBWAY_LOGO_SVG_LIST } from '@/component'; +import { TIMESTAMP } from '@/constant'; +import { cn, isLexicalContent } from '@/util'; -import { LostFoundCommentList } from './CommentList'; import { Lost112ArticleTable } from './Lost112ArticleTable'; import { LostTypeBadge } from './LostTypeBadge'; import { RecommendArticles } from './RecommendArticles'; -import { useGetLostFoundDetail, useLostFoundComment } from '../../_lib'; +import { getLostFoundDetailPost } from '../_lib/getDetailPost'; type Props = { - lostId: number; + id: number; }; -export const LostFoundPostDetail = ({ lostId }: Props) => { - const { data: post } = useGetLostFoundDetail(lostId); - const isLexicalSyntaxContent = isLexicalContent(post.content); +export default function LostFoundPostDetail({ id }: Props) { + const { data: post } = useQuery({ + queryKey: ['lost-found-post', id], + queryFn: getLostFoundDetailPost, + staleTime: 5 * TIMESTAMP.MINUTE, + select: res => res.result, + }); - const { handleChangeComment, handleSubmitComment } = useLostFoundComment(lostId); + if (!post) return null; - const images = post.isFromLost112 - ? [ - { - imageId: getRandomInt(), - imageUrl: post.externalSourceImageUrl, - }, - ] - : post.images; + // const images = post.isFromLost112 + // ? [ + // { + // imageId: getRandomInt(), + // imageUrl: post.externalSourceImageUrl, + // }, + // ] + // : post.images; return ( <>
    - + {/* */}
    @@ -44,10 +51,10 @@ export const LostFoundPostDetail = ({ lostId }: Props) => {
    {post.writer || '๋กœ์ŠคํŠธ 112'} - {formatDate(post.createdAt)} + {formatDateTime(post.createdAt!)}
    - {SUBWAY_LOGO_SVG_LIST[post.subwayLineId]} + {/* {SUBWAY_LOGO_SVG_LIST[post.subwayLineId]} */}
    @@ -69,8 +76,8 @@ export const LostFoundPostDetail = ({ lostId }: Props) => { )}
    - {isLexicalSyntaxContent ? ( - div>div]:p-0', '[&>div>div]:border-none')} /> @@ -81,12 +88,12 @@ export const LostFoundPostDetail = ({ lostId }: Props) => {
    - - */} + {/* + /> */} ); -}; +} diff --git a/services/one-app/src/app/(site)/lost-found/_components/postDetail/LostTypeBadge.tsx b/services/one-app/src/app/(main-service)/lost-found/[id]/_components/LostTypeBadge.tsx similarity index 84% rename from services/one-app/src/app/(site)/lost-found/_components/postDetail/LostTypeBadge.tsx rename to services/one-app/src/app/(main-service)/lost-found/[id]/_components/LostTypeBadge.tsx index 3a75a8596..fded0b7d2 100644 --- a/services/one-app/src/app/(site)/lost-found/_components/postDetail/LostTypeBadge.tsx +++ b/services/one-app/src/app/(main-service)/lost-found/[id]/_components/LostTypeBadge.tsx @@ -1,6 +1,4 @@ -'use client'; - -import type { LostFoundType } from '@/model'; +import type { LostFoundType } from '@/types'; interface Props { lostFoundType: LostFoundType; diff --git a/services/one-app/src/app/(main-service)/lost-found/[id]/_components/RecommendArticles.tsx b/services/one-app/src/app/(main-service)/lost-found/[id]/_components/RecommendArticles.tsx new file mode 100644 index 000000000..1ba652952 --- /dev/null +++ b/services/one-app/src/app/(main-service)/lost-found/[id]/_components/RecommendArticles.tsx @@ -0,0 +1,23 @@ +import { ChevronIcon } from '@/asset/icon'; +import { RecommendArticleCard } from '@/component'; +import { IRecommendPost } from '@/types'; + +interface Props { + posts: IRecommendPost[]; +} + +export const RecommendArticles = ({ posts }: Props) => { + if (!posts?.length) return null; + + return ( +
    +
    + ์ถ”์ฒœ ์Šต๋“๋ฌผ + +
    + {posts.map(post => ( + + ))} +
    + ); +}; diff --git a/services/one-app/src/app/(main-service)/lost-found/[id]/_lib/getComments.ts b/services/one-app/src/app/(main-service)/lost-found/[id]/_lib/getComments.ts new file mode 100644 index 000000000..e9c8ca18f --- /dev/null +++ b/services/one-app/src/app/(main-service)/lost-found/[id]/_lib/getComments.ts @@ -0,0 +1,25 @@ +import { QueryFunction } from '@tanstack/react-query'; + +import { CommentList, IResponse } from '@/types'; + +export const getLostFoundComments: QueryFunction< + IResponse, + [_1: string, id: number, _2: string] +> = async ({ queryKey }) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [_1, id] = queryKey; + const res = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/lost-posts/${id}/comments`, { + next: { + tags: ['lost-found-post', id.toString(), 'comments'], + }, + credentials: 'include', + cache: 'no-store', + }); + + if (!res.ok) { + // This will activate the closest `error.js` Error Boundary + throw new Error('Failed to fetch data'); + } + + return res.json(); +}; diff --git a/services/one-app/src/app/(main-service)/lost-found/[id]/_lib/getDetailPost.ts b/services/one-app/src/app/(main-service)/lost-found/[id]/_lib/getDetailPost.ts new file mode 100644 index 000000000..7f32488e0 --- /dev/null +++ b/services/one-app/src/app/(main-service)/lost-found/[id]/_lib/getDetailPost.ts @@ -0,0 +1,24 @@ +import { QueryFunction } from '@tanstack/react-query'; + +import { IResponse, LostFoundPostDetail } from '@/types'; + +export const getLostFoundDetailPost: QueryFunction< + IResponse, + [_1: string, id: number] +> = async ({ queryKey }) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [_1, id] = queryKey; + const res = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/lost-posts/${id}`, { + next: { + tags: ['lost-found-post', id.toString()], + }, + credentials: 'include', + }); + + if (!res.ok) { + // This will activate the closest `error.js` Error Boundary + throw new Error('Failed to fetch data'); + } + + return res.json(); +}; diff --git a/services/one-app/src/app/(main-service)/lost-found/[id]/_lib/getDetailPostServer.ts b/services/one-app/src/app/(main-service)/lost-found/[id]/_lib/getDetailPostServer.ts new file mode 100644 index 000000000..eb86b64bc --- /dev/null +++ b/services/one-app/src/app/(main-service)/lost-found/[id]/_lib/getDetailPostServer.ts @@ -0,0 +1,26 @@ +import { cookies } from 'next/headers'; + +export const getLostFoundDetailPostServer = async ({ + queryKey, +}: { + queryKey: [string, number]; +}) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [_1, id] = queryKey; + const res = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/lost-posts/${id}`, { + next: { + revalidate: 3600, + tags: ['lost-found-post', id.toString()], + }, + cache: 'force-cache', + credentials: 'include', + headers: { Cookie: (await cookies()).toString() }, + }); + + if (!res.ok) { + // This will activate the closest `error.js` Error Boundary + throw new Error('Failed to fetch data'); + } + + return res.json(); +}; diff --git a/services/one-app/src/app/(main-service)/lost-found/[id]/edit/page.tsx b/services/one-app/src/app/(main-service)/lost-found/[id]/edit/page.tsx new file mode 100644 index 000000000..9f841dad5 --- /dev/null +++ b/services/one-app/src/app/(main-service)/lost-found/[id]/edit/page.tsx @@ -0,0 +1,11 @@ +type Props = { + params: Promise<{ + lostId: string; + }>; +}; + +export default async function LostFoundEditPage({ params }: Props) { + const { lostId } = await params; + + return
    {lostId}
    ; +} diff --git a/services/one-app/src/app/(main-service)/lost-found/[id]/page.tsx b/services/one-app/src/app/(main-service)/lost-found/[id]/page.tsx new file mode 100644 index 000000000..ae4c43f06 --- /dev/null +++ b/services/one-app/src/app/(main-service)/lost-found/[id]/page.tsx @@ -0,0 +1,78 @@ +import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query'; +import type { Metadata } from 'next'; + +import { SUBWAY_LINES } from '@/constant'; +import { extractTextFromLexical } from '@/util'; + +import LostFoundPostDetail from './_components/LostFoundDetail'; +import { getLostFoundComments } from './_lib/getComments'; +import { getLostFoundDetailPostServer } from './_lib/getDetailPostServer'; + +export async function generateMetadata({ params }: Props): Promise { + const { id } = await params; + const post = await getLostFoundDetailPostServer({ queryKey: ['lost-found-post', id] }); + + const subwayLineId = post.result.subwayLineId; + + const baseTitle = (subwayLineId?: string) => + `${ + post.result.title.length > 16 ? post.result.title.slice(0, 16) + '...' : post.result.title + } / ${subwayLineId} ๋ถ„์‹ค๋ฌผ & ์œ ์‹ค๋ฌผ - ์•„ํ•˜์ฒ `; + + const title = + subwayLineId && +subwayLineId !== 0 + ? baseTitle(SUBWAY_LINES.find(subway => subway.id === +subwayLineId)?.name) + : baseTitle('์ง€ํ•˜์ฒ '); + + const baseDescription = + '์ง€ํ•˜์ฒ ์—์„œ ์žƒ์–ด๋ฒ„๋ฆฐ ๋ฌผ๊ฑด์„ ์‰ฝ๊ณ  ๋น ๋ฅด๊ฒŒ ์ฐพ์•„๋ณด์„ธ์š”. ๋ถ„์‹ค๋ฌผ ์ •๋ณด๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ํ™•์ธํ•˜๊ณ  ์ง€ํ•˜์ฒ  ๋…ธ์„ ๋ณ„ ์œ ์‹ค๋ฌผ ์„ผํ„ฐ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์†Œ์ค‘ํ•œ ๋ฌผ๊ฑด์„ ์ฐพ๋Š” ๊ฐ€์žฅ ๋น ๋ฅธ ๋ฐฉ๋ฒ•, ์•„ํ•˜์ฒ ๊ณผ ํ•จ๊ป˜ํ•˜์„ธ์š”.'; + + const image = + subwayLineId && +subwayLineId !== 0 + ? `https://static.dev.ahhachul.com/banners/lost-found/subway-line-${subwayLineId}.png` + : 'https://static.dev.ahhachul.com/banners/lost-found/main.png'; + + return { + title, + description: extractTextFromLexical(post.result.content, baseDescription), + openGraph: { + title, + description: extractTextFromLexical(post.result.content, baseDescription), + images: [ + { + url: image, + width: 800, + height: 400, + }, + ], + }, + }; +} + +type Props = { + params: Promise<{ + id: number; + }>; +}; + +export default async function LostFoundDetailPage(props: Props) { + const { id } = await props.params; + const queryClient = new QueryClient(); + await queryClient.prefetchQuery({ + queryKey: ['lost-found-post', id], + queryFn: getLostFoundDetailPostServer, + }); + await queryClient.prefetchQuery({ + queryKey: ['lost-found-post', id, 'comments'], + queryFn: getLostFoundComments, + }); + const dehydratedState = dehydrate(queryClient); + + return ( +
    + + + +
    + ); +} diff --git a/services/one-app/src/app/(main-service)/lost-found/_components/FilterList.tsx b/services/one-app/src/app/(main-service)/lost-found/_components/FilterList.tsx new file mode 100644 index 000000000..9b1ca87c5 --- /dev/null +++ b/services/one-app/src/app/(main-service)/lost-found/_components/FilterList.tsx @@ -0,0 +1,24 @@ +'use client'; + +import { useSearchParams } from 'next/navigation'; + +import { ResetFilter, DropdownFilter } from '@/component'; +import { subwayLineIdOptions } from '@/constant'; +import { defaultLostFoundFilterValues, lostTypeOptions } from '@/constant/lost-found'; +import { LostFoundType, SubwayLineFilterOptions } from '@/types'; + +export default function Filters() { + const searchParams = useSearchParams(); + const category = (searchParams.get('category') as LostFoundType) ?? LostFoundType.LOST; + const subwayLineId = + (searchParams.get('subwayLineId') as SubwayLineFilterOptions) ?? + SubwayLineFilterOptions.ALL_LINES; + + return ( +
    + + + +
    + ); +} diff --git a/services/one-app/src/app/(main-service)/lost-found/_components/LostFoundPosts.tsx b/services/one-app/src/app/(main-service)/lost-found/_components/LostFoundPosts.tsx new file mode 100644 index 000000000..fc32aef24 --- /dev/null +++ b/services/one-app/src/app/(main-service)/lost-found/_components/LostFoundPosts.tsx @@ -0,0 +1,60 @@ +'use client'; + +import { Fragment, useEffect } from 'react'; +import { useInView } from 'react-intersection-observer'; + +import { InfiniteData, useInfiniteQuery } from '@tanstack/react-query'; +import Link from 'next/link'; +import { useSearchParams } from 'next/navigation'; + +import { ArticleListSuspenseFallback, Post } from '@/component'; +import type { ApiResponse, LostFoundPost, PaginatedList } from '@/types'; + +import { getLostFoundPosts } from '../_lib/getLostFoundPosts'; + +export default function LostFoundPosts() { + const searchParams = useSearchParams(); + + const { data, hasNextPage, fetchNextPage, isFetching, isPending } = useInfiniteQuery< + ApiResponse>, + Error, + InfiniteData>>, + [_1: string, _2: string, _3: string], + string + >({ + queryKey: ['lost-found', 'posts', searchParams.toString()], + queryFn: getLostFoundPosts, + initialPageParam: '', + getNextPageParam: lastPage => lastPage.result.pageToken, + staleTime: 60 * 1000, + gcTime: 300 * 1000, + }); + + const { ref, inView } = useInView({ + delay: 500, + threshold: 0, + }); + + useEffect(() => { + if (inView) { + !isFetching && hasNextPage && fetchNextPage(); + } + }, [inView, isFetching, hasNextPage, fetchNextPage]); + + if (isPending) return ; + + return ( + <> + {data?.pages.map((page, i) => ( + + {page.result.data.map(post => ( + + + + ))} + + ))} +
    + + ); +} diff --git a/services/one-app/src/app/(main-service)/lost-found/_lib/getLostFoundPosts.tsx b/services/one-app/src/app/(main-service)/lost-found/_lib/getLostFoundPosts.tsx new file mode 100644 index 000000000..122d27156 --- /dev/null +++ b/services/one-app/src/app/(main-service)/lost-found/_lib/getLostFoundPosts.tsx @@ -0,0 +1,36 @@ +import { objectToQueryString, removeFalsyValues } from '@ahhachul/utils'; + +import { fetchClient } from '@/lib/fetch-client'; +import { LostFoundType, type ApiResponse, type LostFoundPost, type PaginatedList } from '@/types'; + +type Props = { + pageParam?: string; + queryKey: [_1: string, _2: string, filters: string]; +}; + +export async function getLostFoundPosts({ + pageParam, + queryKey, +}: Props): Promise>> { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [_1, _2, query] = queryKey; + + const filters = new URLSearchParams(query); + + const params = removeFalsyValues({ + ...(filters.get('keyword') && { keyword: filters.get('keyword') || '' }), + ...(filters.get('subwayLineId') && { subwayLineId: filters.get('subwayLineId') || '' }), + pageSize: 10, + ...(pageParam && { pageToken: pageParam }), + ...{ lostType: filters.get('category') || LostFoundType.LOST }, + }); + + return await fetchClient( + `${process.env.NEXT_PUBLIC_BASE_URL}/lost-posts?${objectToQueryString(params)}`, + { + next: { + tags: ['lost-found', 'posts'], + }, + }, + ); +} diff --git a/services/one-app/src/app/(main-service)/lost-found/_lib/metadata.ts b/services/one-app/src/app/(main-service)/lost-found/_lib/metadata.ts new file mode 100644 index 000000000..2444e9838 --- /dev/null +++ b/services/one-app/src/app/(main-service)/lost-found/_lib/metadata.ts @@ -0,0 +1,51 @@ +import type { Metadata } from 'next'; + +import { SUBWAY_LINES } from '@/constant'; +import { LostFoundType } from '@/types'; + +export async function generateLostFoundMetadata( + searchParams: Promise<{ + q?: string; + subwayLineId?: string; + category?: LostFoundType; + }>, +): Promise { + const { subwayLineId } = await searchParams; + + const baseTitle = '์ง€ํ•˜์ฒ  ๋ถ„์‹ค๋ฌผ & ์œ ์‹ค๋ฌผ / 1๋“ฑ ์ง€ํ•˜์ฒ  ๋ฏผ์› & ๋ถ„์‹ค๋ฌผ & ์ปค๋ฎค๋‹ˆํ‹ฐ ์ •๋ณด ์•ฑ - ์•„ํ•˜์ฒ '; + + const baseDescription = + '์ง€ํ•˜์ฒ ์—์„œ ์žƒ์–ด๋ฒ„๋ฆฐ ๋ฌผ๊ฑด์„ ์‰ฝ๊ณ  ๋น ๋ฅด๊ฒŒ ์ฐพ์•„๋ณด์„ธ์š”. ๋ถ„์‹ค๋ฌผ ์ •๋ณด๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ํ™•์ธํ•˜๊ณ  ์ง€ํ•˜์ฒ  ๋…ธ์„ ๋ณ„ ์œ ์‹ค๋ฌผ ์„ผํ„ฐ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์†Œ์ค‘ํ•œ ๋ฌผ๊ฑด์„ ์ฐพ๋Š” ๊ฐ€์žฅ ๋น ๋ฅธ ๋ฐฉ๋ฒ•, ์•„ํ•˜์ฒ ๊ณผ ํ•จ๊ป˜ํ•˜์„ธ์š”.'; + + const title = + subwayLineId && +subwayLineId !== 0 + ? `${SUBWAY_LINES.find(subway => subway.id === +subwayLineId)?.name} ${baseTitle}` + : baseTitle; + + const description = + subwayLineId && +subwayLineId !== 0 + ? `${SUBWAY_LINES.find(subway => subway.id === +subwayLineId)?.name} ${baseDescription}` + : baseDescription; + + const image = + subwayLineId && +subwayLineId !== 0 + ? `https://static.dev.ahhachul.com/banners/lost-found/subway-line-${subwayLineId}.png` + : 'https://static.dev.ahhachul.com/banners/lost-found/main.png'; + + return { + title, + description, + applicationName: '์•„ํ•˜์ฒ  | AhHachul', + openGraph: { + title, + description, + images: [ + { + url: image, + width: 800, + height: 400, + }, + ], + }, + }; +} diff --git a/services/one-app/src/app/(main-service)/lost-found/_lib/prefetchPosts.ts b/services/one-app/src/app/(main-service)/lost-found/_lib/prefetchPosts.ts new file mode 100644 index 000000000..77f577d4a --- /dev/null +++ b/services/one-app/src/app/(main-service)/lost-found/_lib/prefetchPosts.ts @@ -0,0 +1,33 @@ +import type { QueryClient, InfiniteData } from '@tanstack/react-query'; + +import { objectToQueryString } from '@ahhachul/utils'; + +import type { + ApiResponse, + LostFoundPost, + LostFoundType, + PaginatedList, + SubwayLineFilterOptions, +} from '@/types'; + +import { getLostFoundPosts } from './getLostFoundPosts'; + +type SearchParams = { + q?: string; + category?: LostFoundType; + subwayLineId?: SubwayLineFilterOptions; +}; + +export async function prefetchPosts(queryClient: QueryClient, query: SearchParams) { + await queryClient.prefetchInfiniteQuery< + ApiResponse>, + Error, + InfiniteData>>, + [_1: string, _2: string, _3: string], + string + >({ + queryKey: ['lost-found', 'posts', objectToQueryString(query)], + queryFn: getLostFoundPosts, + initialPageParam: '', + }); +} diff --git a/services/one-app/src/app/(main-service)/lost-found/new/page.tsx b/services/one-app/src/app/(main-service)/lost-found/new/page.tsx new file mode 100644 index 000000000..362d117db --- /dev/null +++ b/services/one-app/src/app/(main-service)/lost-found/new/page.tsx @@ -0,0 +1,10 @@ +const NewLostFoundPage = () => { + return ( +
    + {/* + */} +
    + ); +}; + +export default NewLostFoundPage; diff --git a/services/one-app/src/app/(main-service)/lost-found/page.tsx b/services/one-app/src/app/(main-service)/lost-found/page.tsx new file mode 100644 index 000000000..90f6c449b --- /dev/null +++ b/services/one-app/src/app/(main-service)/lost-found/page.tsx @@ -0,0 +1,46 @@ +import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query'; +import { headers } from 'next/headers'; + +import SearchForm from '@/component/SearchForm'; +import type { LostFoundType, SubwayLineFilterOptions } from '@/types'; + +import Filters from './_components/FilterList'; +import LostFoundPosts from './_components/LostFoundPosts'; +import { generateLostFoundMetadata } from './_lib/metadata'; +import { prefetchPosts } from './_lib/prefetchPosts'; + +type Props = { + searchParams: Promise<{ + q?: string; + category?: LostFoundType; + subwayLineId?: SubwayLineFilterOptions; + }>; +}; + +export async function generateMetadata({ searchParams }: Props) { + return generateLostFoundMetadata(searchParams); +} + +export default async function LostFoundPage({ searchParams }: Props) { + const query = await searchParams; + const headersList = await headers(); + const isServerRender = !headersList.get('next-url'); + + let dehydratedState; + + if (isServerRender) { + const queryClient = new QueryClient(); + await prefetchPosts(queryClient, query); + dehydratedState = dehydrate(queryClient); + } + + return ( +
    + + + + + +
    + ); +} diff --git a/services/one-app/src/app/(main-service)/me/page.tsx b/services/one-app/src/app/(main-service)/me/page.tsx new file mode 100644 index 000000000..d10c6242b --- /dev/null +++ b/services/one-app/src/app/(main-service)/me/page.tsx @@ -0,0 +1,3 @@ +export default function MyPage() { + return
    MyPage
    ; +} diff --git a/services/one-app/src/app/(site)/_component/Editor/index.ts b/services/one-app/src/app/(site)/_component/Editor/index.ts deleted file mode 100644 index 3543adf57..000000000 --- a/services/one-app/src/app/(site)/_component/Editor/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './Editor'; -export * from './LexicalSyntaxContentParser'; - -export * from './plugins'; diff --git a/services/one-app/src/app/(site)/_component/LoggedIn.tsx b/services/one-app/src/app/(site)/_component/LoggedIn.tsx deleted file mode 100644 index 9198a9747..000000000 --- a/services/one-app/src/app/(site)/_component/LoggedIn.tsx +++ /dev/null @@ -1,21 +0,0 @@ -'use client'; - -import { useEffect, useState } from 'react'; - -import { AuthService } from '@/common/service/AuthService'; - -// ์—†์–ด์งˆ ์ปดํฌ๋„ŒํŠธ -export default function LoggedIn() { - const [isLoggedIn, setIsLoggedIn] = useState(null); - - useEffect(() => { - // Check login state on the client side - setIsLoggedIn(AuthService.isLoggedIn); - }, []); - - if (isLoggedIn === null) { - return null; - } - - return

    Logged in: {isLoggedIn ? 'O' : 'X'}

    ; -} diff --git a/services/one-app/src/app/(site)/_component/index.ts b/services/one-app/src/app/(site)/_component/index.ts deleted file mode 100644 index e8b6f30bb..000000000 --- a/services/one-app/src/app/(site)/_component/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as LoggedIn } from './LoggedIn'; diff --git a/services/one-app/src/app/(site)/_hook/index.ts b/services/one-app/src/app/(site)/_hook/index.ts deleted file mode 100644 index 44b098bef..000000000 --- a/services/one-app/src/app/(site)/_hook/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './useReport'; diff --git a/services/one-app/src/app/(site)/_lib/comment.ts b/services/one-app/src/app/(site)/_lib/comment.ts deleted file mode 100644 index f44d6b3e4..000000000 --- a/services/one-app/src/app/(site)/_lib/comment.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { useMutation } from '@tanstack/react-query'; - -import { apiClient } from '@/app/api'; -import type { Comment, IResponse } from '@/model'; - -export const deleteComment = (commentId: number) => - apiClient.delete>>(`comments/${commentId}`); - -export const useDeleteComment = () => { - return useMutation({ - mutationFn: deleteComment, - }); -}; - -export const updateComment = async (data: { content: string; commentId: number }) => { - const response = await apiClient.patch>>( - `comments/${data.commentId}`, - { - content: data.content, - }, - ); - return response.data; -}; - -export const useUpdateComment = () => { - return useMutation({ - mutationFn: updateComment, - }); -}; diff --git a/services/one-app/src/app/(site)/_lib/index.ts b/services/one-app/src/app/(site)/_lib/index.ts deleted file mode 100644 index 6e8fe75e5..000000000 --- a/services/one-app/src/app/(site)/_lib/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './comment'; diff --git a/services/one-app/src/app/(site)/layout.tsx b/services/one-app/src/app/(site)/layout.tsx deleted file mode 100644 index d05a3d010..000000000 --- a/services/one-app/src/app/(site)/layout.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import 'react-lazy-load-image-component/src/effects/opacity.css'; - -import type { Metadata } from 'next'; -import { NuqsAdapter } from 'nuqs/adapters/next/app'; - -import { RQProvider, MSWComponent } from '@/app/_components'; -import { Pretendard } from '@/common/assets/fonts/pretendard'; -import { cn } from '@/common/utils/cn'; - -import '../globals.css'; - -export const metadata: Metadata = { - title: 'Create Next App', - description: 'Generated by create next app', -}; - -export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { - return ( - - - - - {children} - - - - ); -} diff --git a/services/one-app/src/app/(site)/login/_lib/checkNickname.ts b/services/one-app/src/app/(site)/login/_lib/checkNickname.ts deleted file mode 100644 index f85a631c5..000000000 --- a/services/one-app/src/app/(site)/login/_lib/checkNickname.ts +++ /dev/null @@ -1,41 +0,0 @@ -import axios from 'axios'; -import { z } from 'zod'; - -import { apiClient } from '@/app/api'; -import { APIResponseCode, RESPONSE_MESSAGES } from '@/common/constants'; -import { sleep } from '@/common/utils'; - -const CheckNicknameResponseSchema = z.object({ - code: z.literal(APIResponseCode.SUCCESS), - message: z.literal(RESPONSE_MESSAGES[APIResponseCode.SUCCESS]), - result: z.object({ - available: z.boolean(), - }), -}); - -type CheckNicknameResponse = z.infer; - -export const checkNickname = async (nickname: string) => { - try { - const [res] = await Promise.all([ - apiClient.post('/members/check-nickname', { - nickname, - }), - sleep(300), - ]); - - return CheckNicknameResponseSchema.parse(res.data); - } catch (error) { - if (axios.isAxiosError(error)) { - // Axios ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ - throw new Error(`Sign in failed: ${error.response?.data?.message || error.message}`); - } else if (error instanceof z.ZodError) { - // Zod ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ - throw new Error(`Validation failed: ${error.errors.map(e => e.message).join(', ')}`); - } else { - // ๊ธฐํƒ€ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ - console.error('Unexpected error during sign in:', error); - throw new Error('An unexpected error occurred during sign in.'); - } - } -}; diff --git a/services/one-app/src/app/(site)/login/_lib/getRedirectUrl.ts b/services/one-app/src/app/(site)/login/_lib/getRedirectUrl.ts deleted file mode 100644 index df26289d7..000000000 --- a/services/one-app/src/app/(site)/login/_lib/getRedirectUrl.ts +++ /dev/null @@ -1,33 +0,0 @@ -import axios from 'axios'; -import { z } from 'zod'; - -import { apiClient } from '@/app/api'; -import { APIResponseCode, RESPONSE_MESSAGES } from '@/common/constants'; -import type { SocialSignInType } from '@/model'; - -const RedirectUrlResponseSchema = z.object({ - code: z.literal(APIResponseCode.SUCCESS), - message: z.literal(RESPONSE_MESSAGES[APIResponseCode.SUCCESS]), - result: z.object({ - redirectUrl: z.string(), - }), -}); - -type RedirectUrlResponse = z.infer; - -export async function getRedirectUrl(params: SocialSignInType): Promise { - try { - const response = await apiClient.get( - `/auth/redirect-url?providerType=${params}`, - ); - - return RedirectUrlResponseSchema.parse(response.data); - } catch (error) { - if (axios.isAxiosError(error) && error.response) { - console.error('Server responded with error:', error.response.data); - } else { - console.error('Error during sign in:', error); - } - throw error; - } -} diff --git a/services/one-app/src/app/(site)/login/_lib/index.ts b/services/one-app/src/app/(site)/login/_lib/index.ts deleted file mode 100644 index c4252451b..000000000 --- a/services/one-app/src/app/(site)/login/_lib/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './utils'; -export * from './checkNickname'; -export * from './requestLogin'; -export * from './getRedirectUrl'; -export * from './useCheckNickname'; -export * from './socialLoginOptions'; diff --git a/services/one-app/src/app/(site)/login/_lib/requestLogin.ts b/services/one-app/src/app/(site)/login/_lib/requestLogin.ts deleted file mode 100644 index aaf254d38..000000000 --- a/services/one-app/src/app/(site)/login/_lib/requestLogin.ts +++ /dev/null @@ -1,49 +0,0 @@ -import axios from 'axios'; -import { z } from 'zod'; - -import { apiClient } from '@/app/api'; -import { APIResponseCode, RESPONSE_MESSAGES } from '@/common/constants'; -import type { SocialSignInType } from '@/model'; - -// TODO, ์‹คํŒจ ์ผ€์ด์Šค ์ถ”๊ฐ€ -export const LoginResponseSchema = z.object({ - code: z.literal(APIResponseCode.SUCCESS), - message: z.literal(RESPONSE_MESSAGES[APIResponseCode.SUCCESS]), - result: z.object({ - memberId: z.string(), - isNeedAdditionalUserInfo: z.boolean(), - accessToken: z.string(), - accessTokenExpiresIn: z.number(), - refreshToken: z.string(), - refreshTokenExpiresIn: z.number(), - }), -}); - -export async function requestLogin({ - providerType, - providerCode, -}: { - providerType: SocialSignInType; - providerCode: string; -}) { - try { - const res = await apiClient.post(`/auth/login`, { - providerType, - providerCode, - }); - - return LoginResponseSchema.parse(res.data); - } catch (error) { - if (axios.isAxiosError(error)) { - // Axios ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ - throw new Error(`Sign in failed: ${error.response?.data?.message || error.message}`); - } else if (error instanceof z.ZodError) { - // Zod ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ - throw new Error(`Validation failed: ${error.errors.map(e => e.message).join(', ')}`); - } else { - // ๊ธฐํƒ€ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ - console.error('Unexpected error during sign in:', error); - throw new Error('An unexpected error occurred during sign in.'); - } - } -} diff --git a/services/one-app/src/app/(site)/login/_lib/useCheckNickname.ts b/services/one-app/src/app/(site)/login/_lib/useCheckNickname.ts deleted file mode 100644 index 4aa82d16f..000000000 --- a/services/one-app/src/app/(site)/login/_lib/useCheckNickname.ts +++ /dev/null @@ -1,89 +0,0 @@ -'use client'; - -import { useState } from 'react'; - -import { useMutation } from '@tanstack/react-query'; - -import { checkNickname } from '@/app/(site)/login/_lib'; -import { useDebounce } from '@/common/hooks'; - -const MAX_LENGTH = 10; -const MIN_LENGTH = 2; - -enum ErrorStatus { - TOO_SHORT = `๋‹‰๋„ค์ž„์€ ${MIN_LENGTH}์ž ์ด์ƒ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.`, - TOO_LONG = `ํ•œ๊ธ€,์˜๋ฌธ ${MAX_LENGTH}์ž ์ดํ•˜๋กœ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.`, - DUPLICATED_NAME = '์ค‘๋ณต์ธ ๋‹‰๋„ค์ž„์ด๋ผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.', - INVALID_FORMAT = '์ง€์›ํ•˜์ง€ ์•Š๋Š” ํ˜•์‹์ž…๋‹ˆ๋‹ค.', -} - -export const useCheckNickname = () => { - const { mutateAsync: nicknameChecking, status } = useMutation({ - mutationFn: checkNickname, - }); - - const [nickname, setNickname] = useState(''); - const [isTouched, setIsTouched] = useState(false); - const [errorMessage, setErrorMessage] = useState(''); - - const isNicknameChecking = status === 'pending'; - const isValidateOk = nickname.length >= MIN_LENGTH && errorMessage === ''; - const isValidateError = isTouched && (nickname.length < MIN_LENGTH || errorMessage !== ''); - - const disabled = - errorMessage !== '' || nickname.length < MIN_LENGTH || nickname.length > MAX_LENGTH; - - const lengthIndicator = `${nickname.length} / ${MAX_LENGTH}`; - - const handleInputChange = (e: React.ChangeEvent) => { - const value = e.target.value.slice(0, MAX_LENGTH); - setNickname(value); - if (!isTouched) setIsTouched(true); - checkNicknameValidity(value); - }; - - const checkNicknameValidity = useDebounce(async (value: string) => { - if (value.length < MIN_LENGTH) { - setErrorMessage(ErrorStatus.TOO_SHORT); - return; - } - if (value.length > MAX_LENGTH) { - setErrorMessage(ErrorStatus.TOO_LONG); - return; - } - - try { - const res = await nicknameChecking(value); - if (!res.result.available) { - setErrorMessage(ErrorStatus.DUPLICATED_NAME); - } else { - setErrorMessage(''); - } - } catch (error) { - setErrorMessage(ErrorStatus.INVALID_FORMAT); - } - }, 500); - - const nickNameStatusMessage = (() => { - if (errorMessage) { - return errorMessage; - } - if (isValidateOk) { - return '์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋‹‰๋„ค์ž„ ์ž…๋‹ˆ๋‹ค.'; - } - return `๋‹‰๋„ค์ž„์€ ${MIN_LENGTH}์ž ์ด์ƒ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.`; - })(); - - return { - nickname, - disabled, - errorMessage, - lengthIndicator, - isTouched, - isValidateOk, - isValidateError, - isNicknameChecking, - nickNameStatusMessage, - handleInputChange, - }; -}; diff --git a/services/one-app/src/app/(site)/login/_lib/utils.tsx b/services/one-app/src/app/(site)/login/_lib/utils.tsx deleted file mode 100644 index 86a56fdad..000000000 --- a/services/one-app/src/app/(site)/login/_lib/utils.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { CheckIcon, AlertCircleIcon } from '@/common/assets/icons'; - -export const renderIndicatorIcon = (isValidateOk: boolean, isValidateError: boolean) => { - if (isValidateOk) { - return ; - } - if (isValidateError) { - return ; - } - return null; -}; diff --git a/services/one-app/src/app/(site)/login/callback/page.tsx b/services/one-app/src/app/(site)/login/callback/page.tsx deleted file mode 100644 index 6d38038e4..000000000 --- a/services/one-app/src/app/(site)/login/callback/page.tsx +++ /dev/null @@ -1,73 +0,0 @@ -'use client'; - -import { Suspense, useRef, useEffect } from 'react'; - -import { useRouter, useSearchParams } from 'next/navigation'; -import { useShallow } from 'zustand/shallow'; - -import { AuthService } from '@/common/service/AuthService'; -import { isValidSocialSignInType } from '@/model/Auth'; -import { useTemporaryAuthStore } from '@/store/auth'; - -import { requestLogin } from '../_lib/requestLogin'; - -function LoginCallback() { - const router = useRouter(); - const isLoadingRef = useRef(false); - const { setTempAuth } = useTemporaryAuthStore( - useShallow(state => ({ - setTempAuth: state.setTempAuth, - })), - ); - - const searchParams = useSearchParams(); - const providerType = searchParams.get('type'); - const providerCode = searchParams.get('code'); - - const handleLogin = async () => { - if (isLoadingRef.current) { - return; - } - - if (!providerType || !providerCode || !isValidSocialSignInType(providerType)) { - return; - } - - try { - isLoadingRef.current = true; - const { result } = await requestLogin({ - providerType, - providerCode, - }); - - const { accessToken, refreshToken, isNeedAdditionalUserInfo } = result; - - if (!isNeedAdditionalUserInfo) { - AuthService.setToken(accessToken, refreshToken); - router.replace('/'); - } else { - setTempAuth({ accessToken, refreshToken }); - router.replace('/login/nickname'); - } - } catch (error) { - console.error(error); - router.replace('/login?error=from_callback'); - } finally { - isLoadingRef.current = false; - } - }; - - useEffect(() => { - handleLogin(); - }, []); - - return null; -} - -export default function LoginCallbackPage() { - return ( - Loading...
    }> - - - ); -} diff --git a/services/one-app/src/app/(site)/login/nickname/page.tsx b/services/one-app/src/app/(site)/login/nickname/page.tsx deleted file mode 100644 index f0b93ebbd..000000000 --- a/services/one-app/src/app/(site)/login/nickname/page.tsx +++ /dev/null @@ -1,128 +0,0 @@ -'use client'; - -import { useMutation } from '@tanstack/react-query'; -import { useRouter } from 'next/navigation'; -import { useShallow } from 'zustand/shallow'; - -import ArrowLeftIcon from '@/common/assets/icons/arrow-left'; -import SpinnerIcon from '@/common/assets/icons/loading-spinner'; -import { AuthService } from '@/common/service/AuthService'; -import { cn } from '@/common/utils/cn'; -import { useTemporaryAuthStore } from '@/store/auth'; - -import { updateUser } from '../../my/_lib/updateUser'; -import { useCheckNickname } from '../_lib/useCheckNickname'; -import { renderIndicatorIcon } from '../_lib/utils'; - -const NicknameSetup = () => { - const router = useRouter(); - const { - nickname, - disabled, - lengthIndicator, - isTouched, - isValidateOk, - isValidateError, - isNicknameChecking, - nickNameStatusMessage, - handleInputChange, - } = useCheckNickname(); - - const { auth, reset: removeTemporaryAuth } = useTemporaryAuthStore( - useShallow(state => ({ - auth: state.auth, - reset: state.reset, - })), - ); - const { mutate: updateUserAndTryLoginProcessDone, status } = useMutation({ - mutationFn: updateUser, - onSuccess: () => { - if (!auth) return; - - const { accessToken, refreshToken } = auth; - AuthService.setToken(accessToken, refreshToken); - removeTemporaryAuth(); - router.replace('/'); - }, - }); - - const handleSubmit = () => { - if (disabled || !auth) return; - updateUserAndTryLoginProcessDone({ nickname, auth }); - }; - - const isUpdating = status === 'pending'; - const isProcessing = isNicknameChecking || isUpdating; - - return ( -
    -
    - -

    ํšŒ์›๊ฐ€์ž…

    -
    - -

    - ๋‹‰๋„ค์ž„์„ ์„ค์ •ํ•ด์ฃผ์„ธ์š” -

    - -
    -
    - -
    - {renderIndicatorIcon(isValidateOk, isValidateError)} -
    -
    - -
    - - {nickNameStatusMessage} - - {lengthIndicator} -
    -
    - - -
    - ); -}; - -export default NicknameSetup; diff --git a/services/one-app/src/app/(site)/lost-found/[lostId]/edit/page.tsx b/services/one-app/src/app/(site)/lost-found/[lostId]/edit/page.tsx deleted file mode 100644 index b82a536e9..000000000 --- a/services/one-app/src/app/(site)/lost-found/[lostId]/edit/page.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { SuspenseQueryBoundary } from '@/common/components'; - -import LostFoundEdit from '../../_components/LostFoundEdit'; - -type Props = { - params: Promise<{ - lostId: string; - }>; -}; - -export default async function LostFoundEditPage({ params }: Props) { - const { lostId } = await params; - - return ( - error
    } suspenseFallback={
    loading
    }> - - - ); -} diff --git a/services/one-app/src/app/(site)/lost-found/[lostId]/page.tsx b/services/one-app/src/app/(site)/lost-found/[lostId]/page.tsx deleted file mode 100644 index 9bf54eac8..000000000 --- a/services/one-app/src/app/(site)/lost-found/[lostId]/page.tsx +++ /dev/null @@ -1,28 +0,0 @@ -'use client'; - -import { SuspenseQueryBoundary, ArticleDetailSuspenseFallback } from '@/common/components'; - -import { LostFoundPostDetail } from '../_components'; - -type Props = { - params: Promise<{ - lostId: number; - }>; -}; - -export default async function LostFoundDetailPage({ params }: Props) { - const { lostId } = await params; - - return ( -
    -
    - error
    } - suspenseFallback={} - > - - -
    - - ); -} diff --git a/services/one-app/src/app/(site)/lost-found/_components/LostFoundCreate.tsx b/services/one-app/src/app/(site)/lost-found/_components/LostFoundCreate.tsx deleted file mode 100644 index b90f3527a..000000000 --- a/services/one-app/src/app/(site)/lost-found/_components/LostFoundCreate.tsx +++ /dev/null @@ -1,9 +0,0 @@ -'use client'; - -import LostFoundForm from './LostFoundForm'; - -const LostFoundCreate = () => { - return ; -}; - -export default LostFoundCreate; diff --git a/services/one-app/src/app/(site)/lost-found/_components/LostFoundDetail.tsx b/services/one-app/src/app/(site)/lost-found/_components/LostFoundDetail.tsx deleted file mode 100644 index 0638d0d16..000000000 --- a/services/one-app/src/app/(site)/lost-found/_components/LostFoundDetail.tsx +++ /dev/null @@ -1,15 +0,0 @@ -'use client'; - -import { useGetLostFoundDetail } from '../_lib/get'; - -type Params = { - lostId: string; -}; - -const LostFoundDetail = ({ lostId }: Params) => { - const { data } = useGetLostFoundDetail(Number(lostId)); - - return
    {data?.title}
    ; -}; - -export default LostFoundDetail; diff --git a/services/one-app/src/app/(site)/lost-found/_components/LostFoundEdit.tsx b/services/one-app/src/app/(site)/lost-found/_components/LostFoundEdit.tsx deleted file mode 100644 index 85874bedb..000000000 --- a/services/one-app/src/app/(site)/lost-found/_components/LostFoundEdit.tsx +++ /dev/null @@ -1,14 +0,0 @@ -'use client'; - -import LostFoundForm from './LostFoundForm'; - -import useFormAdapter from '../_hook/useFormAdapter'; - -const LostFoundEdit = ({ lostId }: { lostId: string }) => { - const lostFoundFormData = useFormAdapter({ - lostId: Number(lostId), - }); - return ; -}; - -export default LostFoundEdit; diff --git a/services/one-app/src/app/(site)/lost-found/_components/LostFoundForm.tsx b/services/one-app/src/app/(site)/lost-found/_components/LostFoundForm.tsx deleted file mode 100644 index 0a2ea0f64..000000000 --- a/services/one-app/src/app/(site)/lost-found/_components/LostFoundForm.tsx +++ /dev/null @@ -1,208 +0,0 @@ -'use client'; - -import { useState } from 'react'; - -import { type EditorState } from 'lexical'; -import { useRouter } from 'next/navigation'; - -import { Editor } from '@/app/(site)/_component/Editor'; -import ArrowLeftIcon from '@/common/assets/icons/arrow-left'; -import CloseCircleIcon from '@/common/assets/icons/close-circle'; -import ImagePlaceHolder from '@/common/assets/icons/image-placeholder'; -import createFormData from '@/lib/createFormData'; -import type { LostFoundFormData } from '@/model/LostFound'; - -import SelectLineDrawer from './SelectLineDrawer'; - -import useFormImage from '../_hook/useFormImage'; -import useFormInitialize from '../_hook/useFormInitialize'; -import { usePostLostFound } from '../_lib/post'; - -type Props = { - lostId?: string | null; - initialFormData?: LostFoundFormData | null; -}; - -const LostFoundForm = ({ lostId = null, initialFormData = null }: Props) => { - const router = useRouter(); - - const { mutate: lostFoundMutate } = usePostLostFound((id: string) => { - router.push(`/lost-found/${id}`); - }); - - const { images, setImages, removeImageIds, handleFileChange, onDeleteImage } = useFormImage(); - - const [subwayLineId, setSubwayLineId] = useState(1); - const [title, setTitle] = useState(''); - const [lostType, setLostType] = useState('LOST'); - const [editorContent, setEditorContent] = useState(null); - - const isActiveSubmitButton = !!title && !!subwayLineId && !!editorContent; - - const onSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - - const contentData = { - title, - content: editorContent, - subwayLineId, - lostType, - ...(lostId && removeImageIds.length && { removeImageIds }), - }; - - const postData = createFormData({ - jsonDataKey: 'content', - jsonData: contentData, - fileDataKey: 'files', - fileData: images.flatMap(image => (image.data !== null ? [image.data] : [])), - }); - lostFoundMutate({ lostId, postData }); - }; - - const onChangeEditorContent = (editorState: EditorState | null) => { - if (editorState) { - setEditorContent(JSON.stringify(editorState.toJSON())); - } else { - setEditorContent(null); - } - }; - - useFormInitialize({ - initialFormData, - initCallback: ({ title, initialContent, subwayLineId, images }) => { - setTitle(title); - setEditorContent(initialContent); - setSubwayLineId(subwayLineId); - setImages(images); - }, - }); - - return ( -
    -
    - - -
    -
    -
    -

    ์œ ์‹ค๋ฌผ ์ƒ์„ธ์ •๋ณด

    -
    - - {images.map((image, index) => { - return ( -
    - {`Uploaded - -
    - ); - })} -
    -
    - -
    -
    -

    ์นดํ…Œ๊ณ ๋ฆฌ

    - -
    -
    - setLostType(e.target.value)} - className="peer/lost hidden" - /> - - - setLostType(e.target.value)} - className="peer/acquire hidden" - /> - -
    -
    -
    -
    -

    ํ˜ธ์„  ์„ ํƒ

    - -
    - -
    -
    -
    -

    ์ œ๋ชฉ

    - -
    - setTitle(e.target.value)} - className="w-full border py-3 pl-3 pr-4 outline-none rounded-[5px] text-body-large-semi text-gray-90 placeholder:text-gray-70" - /> -
    -
    -
    -

    ์ž์„ธํ•œ ์„ค๋ช…

    - -
    - -
    -
    -
    - ); -}; - -export default LostFoundForm; diff --git a/services/one-app/src/app/(site)/lost-found/_components/SelectLineDrawer.tsx b/services/one-app/src/app/(site)/lost-found/_components/SelectLineDrawer.tsx deleted file mode 100644 index 6a50cf469..000000000 --- a/services/one-app/src/app/(site)/lost-found/_components/SelectLineDrawer.tsx +++ /dev/null @@ -1,135 +0,0 @@ -'use client'; - -import { useState, useEffect, useCallback } from 'react'; - -import clsx from 'clsx'; - -import ArrowDownIcon from '@/common/assets/icons/arrow-down'; -import { Carousel, CarouselContent, CarouselItem } from '@/common/components/Carousel'; -import { type CarouselApi } from '@/common/components/Carousel'; -import { - Drawer, - DrawerTrigger, - DrawerClose, - DrawerContent, - DrawerHeader, - DrawerFooter, - DrawerTitle, - DrawerDescription, -} from '@/common/components/Drawer'; -import SUBWAY_LINES from '@/common/constants/subwayLines'; -import { cn } from '@/common/utils/cn'; - -const CAROUSEL_SUBWAY_LINES = SUBWAY_LINES.reduce( - (acc, _, index) => { - if (index % 12 === 0) { - acc.push(SUBWAY_LINES.slice(index, index + 12)); - } - return acc; - }, - [] as Array<{ name: string; id: number }[]>, -); - -const DotButton = ({ active, onClick }: { active: boolean; onClick: () => void }) => { - const buttonClass = clsx('h-2 rounded-full cursor-pointer transition-width duration-300', { - 'w-[15px] bg-[#33333E]': active, - 'w-2 bg-[#CED0DD]': !active, - }); - - return - - -
    - - ํ˜ธ์„  ๋ณ€๊ฒฝ - - ์†Œ์‹์ด ๊ถ๊ธˆํ•œ ํ˜ธ์„ ์„ ์„ ํƒํ•ด์ฃผ์„ธ์š”. - - - - - {CAROUSEL_SUBWAY_LINES.map(lines => { - return ( - - {lines.map(line => { - return ( -
    - - -
    - ); - })} -
    - ); - })} -
    -
    -
    - {CAROUSEL_SUBWAY_LINES.map((_, index) => { - return ( - onClickDotButton(index)} - /> - ); - })} -
    - - - - - -
    -
    - - ); -}; - -export default SelectLineDrawer; diff --git a/services/one-app/src/app/(site)/lost-found/_components/index.ts b/services/one-app/src/app/(site)/lost-found/_components/index.ts deleted file mode 100644 index a43e09157..000000000 --- a/services/one-app/src/app/(site)/lost-found/_components/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './postDetail'; -export * from './searchResults'; diff --git a/services/one-app/src/app/(site)/lost-found/_components/postDetail/CommentList.tsx b/services/one-app/src/app/(site)/lost-found/_components/postDetail/CommentList.tsx deleted file mode 100644 index 66156939b..000000000 --- a/services/one-app/src/app/(site)/lost-found/_components/postDetail/CommentList.tsx +++ /dev/null @@ -1,42 +0,0 @@ -'use client'; - -import { BookmarkIcon } from '@/common/assets/icons'; -import { - BaseCommentList, - SuspenseQueryBoundary, - CommentListSuspenseFallback, -} from '@/common/components'; - -import { useGetLostFoundComments } from '../../_lib'; - -interface Props { - commentCnt: number; - articleId: number; -} - -export const CommentListInner = ({ articleId }: Pick) => { - const { data } = useGetLostFoundComments(articleId); - - return ; -}; - -export const LostFoundCommentList = ({ commentCnt, articleId }: Props) => { - return ( -
    -
    -
    - ๋Œ“๊ธ€ - {commentCnt} -
    - -
    - } - suspenseFallback={} - keys={[articleId]} - > - - -
    - ); -}; diff --git a/services/one-app/src/app/(site)/lost-found/_components/postDetail/index.ts b/services/one-app/src/app/(site)/lost-found/_components/postDetail/index.ts deleted file mode 100644 index 81b45b21c..000000000 --- a/services/one-app/src/app/(site)/lost-found/_components/postDetail/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './PostDetail'; -export * from './CommentList'; -export * from './LostTypeBadge'; -export * from './Lost112ArticleTable'; -export * from './RecommendArticles'; diff --git a/services/one-app/src/app/(site)/lost-found/_components/searchResults/filterList/FilterList.tsx b/services/one-app/src/app/(site)/lost-found/_components/searchResults/filterList/FilterList.tsx deleted file mode 100644 index 0fa4a64fe..000000000 --- a/services/one-app/src/app/(site)/lost-found/_components/searchResults/filterList/FilterList.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { ResetFilter, SearchFilter, DropdownFilter } from '@/common/components'; -import { subwayLineIdOptions } from '@/common/constants'; -import { type LostFoundFilters, LostFoundType } from '@/model'; -import { FilterState } from '@/store'; - -const lostTypeOptions = { - [LostFoundType.LOST]: '๋ถ„์‹ค๋ฌผ', - [LostFoundType.ACQUIRE]: '์Šต๋“๋ฌผ', -}; - -interface LostFoundFilterListProps extends Omit, 'loaded'> {} - -export const LostFoundFilterList = ({ - filters, - activatedCount, - handleSelect, - handleReset, -}: LostFoundFilterListProps) => { - return ( -
    - -
    - - - -
    -
    - ); -}; diff --git a/services/one-app/src/app/(site)/lost-found/_components/searchResults/filterList/index.ts b/services/one-app/src/app/(site)/lost-found/_components/searchResults/filterList/index.ts deleted file mode 100644 index e20122849..000000000 --- a/services/one-app/src/app/(site)/lost-found/_components/searchResults/filterList/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './FilterList'; diff --git a/services/one-app/src/app/(site)/lost-found/_components/searchResults/index.ts b/services/one-app/src/app/(site)/lost-found/_components/searchResults/index.ts deleted file mode 100644 index a97766c6d..000000000 --- a/services/one-app/src/app/(site)/lost-found/_components/searchResults/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './filterList'; -export * from './searchedList'; diff --git a/services/one-app/src/app/(site)/lost-found/_components/searchResults/searchedList/SearchedList.tsx b/services/one-app/src/app/(site)/lost-found/_components/searchResults/searchedList/SearchedList.tsx deleted file mode 100644 index 596a26b7f..000000000 --- a/services/one-app/src/app/(site)/lost-found/_components/searchResults/searchedList/SearchedList.tsx +++ /dev/null @@ -1,56 +0,0 @@ -'use client'; - -import { useMemo } from 'react'; - -import Link from 'next/link'; - -import { useGetLostFoundList } from '@/app/(site)/lost-found/_lib/get'; -import { ArticleCard, EmptyArticleList } from '@/common/components'; -import { useIntersectionObserver } from '@/common/hooks'; -import { flattenInfinityList, formatSubwayLineId } from '@/common/utils'; -import type { LostFoundFilters } from '@/model'; - -interface Props { - keyword: string | null; - filters: LostFoundFilters; -} - -export const LostFoundSearchedList = ({ keyword, filters }: Props) => { - const temporaryUserFavoriteLineId = 1; // TODO: ์ถ”ํ›„ ์œ ์ €๊ฐ€ ์ฆ๊ฒจ์ฐพ๋Š” ์—ญ ์„ค์žฅํ•˜๋Š” ํ”ผ์ณ ๊ฐœ๋ฐœ ํ›„ ์ˆ˜์ •ํ•˜๊ธฐ - const subwayLineId = useMemo( - () => formatSubwayLineId(filters.subwayLineId, temporaryUserFavoriteLineId), - [filters.subwayLineId], - ); - - const { data, hasNextPage, isFetchingNextPage, fetchNextPage } = useGetLostFoundList({ - pageSize: 40, - keyword, - subwayLineId, - lostType: filters.lostType, - }); - - const lostArticles = flattenInfinityList(data); - const intersectCallback = () => !isFetchingNextPage && fetchNextPage(); - const { ref: loadMoreRef } = useIntersectionObserver({ - callback: intersectCallback, - }); - - if (!lostArticles.length) return ; - - return ( -
    - {lostArticles.map((item, idx) => ( - - - - ))} - {hasNextPage && ( - - ๋” ๋ณด๊ธฐ - - )} -
    - ); -}; - -export default LostFoundSearchedList; diff --git a/services/one-app/src/app/(site)/lost-found/_components/searchResults/searchedList/index.ts b/services/one-app/src/app/(site)/lost-found/_components/searchResults/searchedList/index.ts deleted file mode 100644 index 99498b23f..000000000 --- a/services/one-app/src/app/(site)/lost-found/_components/searchResults/searchedList/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './SearchedList'; diff --git a/services/one-app/src/app/(site)/lost-found/_hook/useFormAdapter.tsx b/services/one-app/src/app/(site)/lost-found/_hook/useFormAdapter.tsx deleted file mode 100644 index 0b25f87cb..000000000 --- a/services/one-app/src/app/(site)/lost-found/_hook/useFormAdapter.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { useMemo } from 'react'; - -import type { LostFoundFormData } from '@/model/LostFound'; - -import { useGetLostFoundDetail } from '../_lib/get'; - -type UseLostFoundFormAdapter = ({ lostId }: { lostId: number }) => LostFoundFormData; - -const DEFAULT_DATA: LostFoundFormData = { - title: '', - initialContent: '', - subwayLineId: 1, - lostType: 'LOST' as LostFoundFormData['lostType'], - images: [], -} as const; - -const useLostFoundFormAdapter: UseLostFoundFormAdapter = ({ lostId }) => { - const { data } = useGetLostFoundDetail(lostId); - - const result = useMemo(() => { - if (!data) { - return DEFAULT_DATA; - } - - return { - title: data.title ?? '', - initialContent: data.content ?? '', - subwayLineId: data.subwayLineId ?? 1, - lostType: data.lostType ?? 'LOST', - images: - data.images.map(image => ({ - id: image.imageId ?? null, - data: null, - url: image.imageUrl, - })) ?? [], - }; - }, [data]); - - return result; -}; - -export default useLostFoundFormAdapter; diff --git a/services/one-app/src/app/(site)/lost-found/_hook/useFormImage.tsx b/services/one-app/src/app/(site)/lost-found/_hook/useFormImage.tsx deleted file mode 100644 index 933dd0126..000000000 --- a/services/one-app/src/app/(site)/lost-found/_hook/useFormImage.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { useState } from 'react'; - -import type { DetailImages } from '@/model/LostFound'; - -const MAX_IMAGE_LENGTH = 5; -const MAX_FILE_SIZE = 10 * 1024 * 1024; - -const useFormImage = () => { - const [images, setImages] = useState([]); - const [removeImageIds, setRemoveImageIds] = useState([]); - - const handleFileChange = (e: React.ChangeEvent) => { - if (images.length >= MAX_IMAGE_LENGTH) { - alert(`์ด๋ฏธ์ง€๋Š” ์ตœ๋Œ€ ${MAX_IMAGE_LENGTH}์žฅ๊นŒ์ง€ ๋“ฑ๋ก๋ฉ๋‹ˆ๋‹ค.`); - return; - } - - const fileBlob = e.target.files?.[0]; - if (!fileBlob) return; - if (fileBlob.size > MAX_FILE_SIZE) { - alert(`ํŒŒ์ผ ์šฉ๋Ÿ‰์€ ์ตœ๋Œ€ ${MAX_FILE_SIZE / 1024 / 1024}MB๊นŒ์ง€ ํ—ˆ์šฉ๋ฉ๋‹ˆ๋‹ค.`); - return; - } - - const fileUrl = URL.createObjectURL(fileBlob); - setImages(prev => [...prev, { id: null, data: fileBlob, url: fileUrl }]); - e.target.value = ''; - }; - - const onDeleteImage = (index: number) => { - const targetImageId = images[index].id; - if (targetImageId !== null) { - setRemoveImageIds(prev => [...prev, targetImageId]); - } - setImages(prev => prev.filter((_, i) => i !== index)); - }; - - return { images, setImages, removeImageIds, handleFileChange, onDeleteImage }; -}; - -export default useFormImage; diff --git a/services/one-app/src/app/(site)/lost-found/_hook/useFormInitialize.tsx b/services/one-app/src/app/(site)/lost-found/_hook/useFormInitialize.tsx deleted file mode 100644 index 6e0210599..000000000 --- a/services/one-app/src/app/(site)/lost-found/_hook/useFormInitialize.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { useEffect } from 'react'; - -import type { LostFoundFormData } from '@/model/LostFound'; - -type Params = { - initialFormData: LostFoundFormData | null; - initCallback: (params: LostFoundFormData) => void; -}; - -const useLostFoundFormInitialize = ({ initialFormData, initCallback }: Params) => { - useEffect(() => { - if (initialFormData) { - initCallback(initialFormData); - } - }, [initialFormData]); -}; - -export default useLostFoundFormInitialize; diff --git a/services/one-app/src/app/(site)/lost-found/_lib/comments.ts b/services/one-app/src/app/(site)/lost-found/_lib/comments.ts deleted file mode 100644 index 8abd847a6..000000000 --- a/services/one-app/src/app/(site)/lost-found/_lib/comments.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { useSuspenseQuery, useMutation } from '@tanstack/react-query'; - -import { apiClient } from '@/app/api'; -import { TIMESTAMP } from '@/common/constants'; -import { generateQueryKey } from '@/common/utils'; -import type { IResponse, Comment, CommentList } from '@/model'; - -const getLostFoundComments = (articleId: number) => - apiClient.get>(`/lost-posts/${articleId}/comments`); - -export const useGetLostFoundComments = (articleId: number) => - useSuspenseQuery({ - queryKey: generateQueryKey(['LOST_FOUND']).comments(articleId), - queryFn: () => getLostFoundComments(articleId), - staleTime: 5 * TIMESTAMP.MINUTE, // default: 5๋ถ„, ๊ธ€ ์ˆ˜์ • ์‹œ์—๋Š” ๋”ฐ๋กœ ์—…๋ฐ์ดํŠธ ๊ด€๋ฆฌ - select: res => { - return res.data.result; - }, - }); - -export const postLostFoundComment = async (data: { - articleId: number; - content: string; - upperCommentId?: number; - isPrivate?: boolean; -}) => { - const { articleId, content, upperCommentId = null, isPrivate = false } = data; - const response = await apiClient.post< - IResponse> - >(`/lost-found/${articleId}/comments`, { - content, - upperCommentId, - isPrivate, - }); - return response.data; -}; - -export const useLostFoundPostComment = () => { - return useMutation({ - mutationFn: postLostFoundComment, - }); -}; diff --git a/services/one-app/src/app/(site)/lost-found/_lib/get.ts b/services/one-app/src/app/(site)/lost-found/_lib/get.ts deleted file mode 100644 index b0435c9d2..000000000 --- a/services/one-app/src/app/(site)/lost-found/_lib/get.ts +++ /dev/null @@ -1,44 +0,0 @@ -'use client'; - -import { useSuspenseQuery, useSuspenseInfiniteQuery } from '@tanstack/react-query'; - -import { apiClient } from '@/app/api'; -import { TIMESTAMP } from '@/common/constants'; -import { generateQueryKey, objectToQueryString } from '@/common/utils'; -import type { - IResponse, - ListResponseWithPagination, - LostFoundPost, - LostFoundPostDetail, - LostFoundListParams, -} from '@/model'; - -const getLostFoundList = (params: LostFoundListParams) => - apiClient.get>>( - `/lost-posts?${objectToQueryString(params, { removeZero: true })}`, - ); - -export const useGetLostFoundList = (params: LostFoundListParams) => - useSuspenseInfiniteQuery({ - queryKey: generateQueryKey(['LOST_FOUND']).list({ params }), - queryFn: ({ pageParam = params.pageToken }) => - getLostFoundList({ - ...params, - ...(pageParam && { pageToken: pageParam }), - }), - initialPageParam: '', - getNextPageParam: lastPage => lastPage.data.result.pageToken, - gcTime: TIMESTAMP.MINUTE, - staleTime: TIMESTAMP.SECOND, - }); - -const getLostFoundDetail = (id: number) => - apiClient.get>(`/lost-posts/${id}`); - -export const useGetLostFoundDetail = (id: number) => - useSuspenseQuery({ - queryKey: generateQueryKey(['LOST_FOUND']).detail(id), - queryFn: () => getLostFoundDetail(id), - staleTime: 5 * TIMESTAMP.MINUTE, - select: res => res.data.result, - }); diff --git a/services/one-app/src/app/(site)/lost-found/_lib/index.ts b/services/one-app/src/app/(site)/lost-found/_lib/index.ts deleted file mode 100644 index 54aaf8468..000000000 --- a/services/one-app/src/app/(site)/lost-found/_lib/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './get'; -export * from './comments'; -export * from './useLostFoundComment'; -export * from './useLostFoundFilterStore'; diff --git a/services/one-app/src/app/(site)/lost-found/_lib/post.ts b/services/one-app/src/app/(site)/lost-found/_lib/post.ts deleted file mode 100644 index 44e6b6153..000000000 --- a/services/one-app/src/app/(site)/lost-found/_lib/post.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { useMutation } from '@tanstack/react-query'; - -import { apiClient } from '@/app/api'; -import type { ErrorCode } from '@/model/Error'; - -type PostLostFoundParams = { - lostId: string | null; - postData: FormData; -}; - -const postLostFound = ({ lostId, postData }: PostLostFoundParams) => - apiClient.post(`/lost-posts/${lostId || ''}`, postData, { - headers: { - 'Content-Type': 'multipart/form-data', - }, - }); - -export const usePostLostFound = (successCallback: (id: string) => void) => { - return useMutation({ - mutationFn: (params: PostLostFoundParams) => postLostFound(params), - onError: () => { - alert('์œ ์‹ค๋ฌผ ๋“ฑ๋ก ์—๋Ÿฌ ๋ฐœ์ƒ'); - }, - onSuccess: (res: { data: { code: ErrorCode; result: { id: string } } }) => { - const { - data: { - code, - result: { id }, - }, - } = res; - - if (code === '100') { - successCallback(id); - } - }, - }); -}; diff --git a/services/one-app/src/app/(site)/lost-found/_lib/useLostFoundComment.ts b/services/one-app/src/app/(site)/lost-found/_lib/useLostFoundComment.ts deleted file mode 100644 index 4944daf74..000000000 --- a/services/one-app/src/app/(site)/lost-found/_lib/useLostFoundComment.ts +++ /dev/null @@ -1,27 +0,0 @@ -'use client'; - -import { useRef } from 'react'; - -import type { EditorState } from 'lexical'; - -import { useLostFoundPostComment } from './comments'; - -export const useLostFoundComment = (articleId: number) => { - const content = useRef(''); - const { mutate: postComment } = useLostFoundPostComment(); - - const handleChangeComment = (val: EditorState | null) => { - content.current = JSON.stringify(val ? val.toJSON() : null); - }; - - const handleSubmitComment = () => { - postComment({ - articleId, - content: content.current, - }); - }; - return { - handleChangeComment, - handleSubmitComment, - }; -}; diff --git a/services/one-app/src/app/(site)/lost-found/_lib/useLostFoundFilterStore.ts b/services/one-app/src/app/(site)/lost-found/_lib/useLostFoundFilterStore.ts deleted file mode 100644 index e558fa413..000000000 --- a/services/one-app/src/app/(site)/lost-found/_lib/useLostFoundFilterStore.ts +++ /dev/null @@ -1,38 +0,0 @@ -'use client'; - -import { useQueryState } from 'nuqs'; - -import { LostFoundType, type LostFoundFilters } from '@/model'; -import { SubwayLineFilterOptions } from '@/model/Subway'; -import { FilterState, APP_UNIQUE_FILTER_ID_LIST, createFilterStoreWithPersist } from '@/store'; - -const LOST_FOUND_FILTER_DEFAULT_VALUES: LostFoundFilters = { - lostType: LostFoundType.LOST, - subwayLineId: SubwayLineFilterOptions.ALL_LINES, -} as const; - -export const useLostFoundFilters = () => { - const [keyword] = useQueryState('keyword'); - const { filters, loaded, activatedCount, handleSelect, handleReset } = - createFilterStoreWithPersist( - LOST_FOUND_FILTER_DEFAULT_VALUES, - APP_UNIQUE_FILTER_ID_LIST.LOST_FOUND, - )(); - - const boundaryKeys = [...Object.values(filters), keyword]; - - const getFilterProps = (): Omit, 'loaded'> => ({ - filters, - activatedCount, - handleSelect, - handleReset, - }); - - return { - loaded, - filters, - keyword, - boundaryKeys, - getFilterProps, - }; -}; diff --git a/services/one-app/src/app/(site)/lost-found/create/page.tsx b/services/one-app/src/app/(site)/lost-found/create/page.tsx deleted file mode 100644 index 6161d63af..000000000 --- a/services/one-app/src/app/(site)/lost-found/create/page.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import LostFoundCreate from '../_components/LostFoundCreate'; - -const LostFoundCreatePage = () => { - return ( -
    - -
    - ); -}; - -export default LostFoundCreatePage; diff --git a/services/one-app/src/app/(site)/lost-found/page.tsx b/services/one-app/src/app/(site)/lost-found/page.tsx deleted file mode 100644 index e3da5a372..000000000 --- a/services/one-app/src/app/(site)/lost-found/page.tsx +++ /dev/null @@ -1,38 +0,0 @@ -'use client'; - -import { Suspense } from 'react'; - -import { Spinner, SuspenseQueryBoundary, ArticleListSuspenseFallback } from '@/common/components'; - -import { LostFoundFilterList, LostFoundSearchedList } from './_components'; -import { useLostFoundFilters } from './_lib'; - -function LostFound() { - const { loaded, keyword, filters, boundaryKeys, getFilterProps } = useLostFoundFilters(); - - return loaded ? ( -
    -
    - - {}} - errorFallback={
    error
    } - suspenseFallback={} - > - -
    -
    -
    - ) : ( - - ); -} - -export default function LostFoundPage() { - return ( - }> - - - ); -} diff --git a/services/one-app/src/app/(site)/my/_lib/index.ts b/services/one-app/src/app/(site)/my/_lib/index.ts deleted file mode 100644 index e6eb352ab..000000000 --- a/services/one-app/src/app/(site)/my/_lib/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './updateUser'; diff --git a/services/one-app/src/app/(site)/my/_lib/updateUser.ts b/services/one-app/src/app/(site)/my/_lib/updateUser.ts deleted file mode 100644 index 1dc17abaa..000000000 --- a/services/one-app/src/app/(site)/my/_lib/updateUser.ts +++ /dev/null @@ -1,49 +0,0 @@ -import axios from 'axios'; -import { z } from 'zod'; - -import { API_BASE_URL, RESPONSE_MESSAGES, APIResponseCode } from '@/common/constants'; -import type { TemporaryUserAuthData } from '@/store'; - -const GenderSchema = z.enum(['MALE', 'FEMALE']).nullable(); - -const AgeRangeSchema = z - .enum(['1', '10', '20', '30', '40', '50', '60', '70', '80', '90']) - .nullable(); - -const UpdateUserResponseSchema = z.object({ - code: z.literal(APIResponseCode.SUCCESS), - message: z.literal(RESPONSE_MESSAGES[APIResponseCode.SUCCESS]), - result: z.object({ - nickname: z.string(), - gender: GenderSchema, - ageRange: AgeRangeSchema, - }), -}); - -type UpdateUserResponse = z.infer; - -export const updateUser = async (data: { nickname: string; auth: TemporaryUserAuthData }) => { - try { - const accessToken = data.auth.accessToken; - const res = await axios.patch( - `${API_BASE_URL}/members`, - { nickname: data.nickname }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - - return UpdateUserResponseSchema.parse(res.data); - } catch (error) { - if (axios.isAxiosError(error)) { - throw new Error(`Update user failed: ${error.response?.data?.message || error.message}`); - } else if (error instanceof z.ZodError) { - throw new Error(`Validation failed: ${error.errors.map(e => e.message).join(', ')}`); - } else { - console.error('Unexpected error during user update:', error); - throw new Error('An unexpected error occurred during user update.'); - } - } -}; diff --git a/services/one-app/src/app/(site)/not-found.tsx b/services/one-app/src/app/(site)/not-found.tsx deleted file mode 100644 index 2e7973e44..000000000 --- a/services/one-app/src/app/(site)/not-found.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { NextPage } from 'next'; -import Link from 'next/link'; - -const NotFound: NextPage = () => { - return ( -
    -
    ์ด ํŽ˜์ด์ง€๋Š” ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ํŽ˜์ด์ง€๋ฅผ ๊ตฌ๊ฒฝํ•ด๋ณด์„ธ์š”.
    - ์ด๋™ -
    - ); -}; - -export default NotFound; diff --git a/services/one-app/src/app/(site)/page.tsx b/services/one-app/src/app/(site)/page.tsx deleted file mode 100644 index ddb477586..000000000 --- a/services/one-app/src/app/(site)/page.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import Link from 'next/link'; - -import LoggedIn from './_component/LoggedIn'; - -export default function Home() { - return ( -
    -
    - ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ - - ์œ ์‹ค๋ฌผ ํŽ˜์ด์ง€ - ์œ ์‹ค๋ฌผ ๋“ฑ๋กํ•˜๊ธฐ -
    -
    - ); -} diff --git a/services/one-app/src/app/(user)/messages/page.tsx b/services/one-app/src/app/(user)/messages/page.tsx new file mode 100644 index 000000000..b1b1bfee6 --- /dev/null +++ b/services/one-app/src/app/(user)/messages/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return
    MyPage
    ; +} diff --git a/services/one-app/src/app/(user)/notifications/page.tsx b/services/one-app/src/app/(user)/notifications/page.tsx new file mode 100644 index 000000000..cc93ea042 --- /dev/null +++ b/services/one-app/src/app/(user)/notifications/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return
    ์•Œ๋žŒ
    ; +} diff --git a/services/one-app/src/app/(user)/user/[username]/page.tsx b/services/one-app/src/app/(user)/user/[username]/page.tsx new file mode 100644 index 000000000..988ec332b --- /dev/null +++ b/services/one-app/src/app/(user)/user/[username]/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return
    ๋งˆ์ด ํŽ˜์ด์ง€
    ; +} diff --git a/services/one-app/src/app/_components/AuthSession.tsx b/services/one-app/src/app/_components/AuthSession.tsx new file mode 100644 index 000000000..687c52f79 --- /dev/null +++ b/services/one-app/src/app/_components/AuthSession.tsx @@ -0,0 +1,9 @@ +'use client'; + +type Props = { + children: React.ReactNode; +}; + +export default function AuthSession({ children }: Props) { + return
    {children}
    ; +} diff --git a/services/one-app/src/app/_components/Header.tsx b/services/one-app/src/app/_components/Header.tsx new file mode 100644 index 000000000..cbd0c7526 --- /dev/null +++ b/services/one-app/src/app/_components/Header.tsx @@ -0,0 +1,88 @@ +import Link from 'next/link'; + +export default function Header() { + return ( +
    + + + +
      +
    • + + + + + + + + +
    • +
    • + + + + + + +
    • +
    +
    + ); +} diff --git a/services/one-app/src/app/_components/NavMenu.tsx b/services/one-app/src/app/_components/NavMenu.tsx new file mode 100644 index 000000000..cd988f38b --- /dev/null +++ b/services/one-app/src/app/_components/NavMenu.tsx @@ -0,0 +1,273 @@ +'use client'; + +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; + +import { cn } from '@/util'; + +export default function NavMenu() { + const pathname = usePathname(); + + if (['/', '/community', '/lost-found', '/complaint', '/me'].includes(pathname)) { + return ( + + ); + } else { + return null; + } +} diff --git a/services/one-app/src/app/_components/RQProvider.tsx b/services/one-app/src/app/_components/RQProvider.tsx deleted file mode 100644 index c4333aae7..000000000 --- a/services/one-app/src/app/_components/RQProvider.tsx +++ /dev/null @@ -1,33 +0,0 @@ -'use client'; - -import React, { useState } from 'react'; - -import { QueryClientProvider, QueryClient } from '@tanstack/react-query'; -import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; - -type Props = { - children: React.ReactNode; -}; - -export const RQProvider = ({ children }: Props) => { - const [client] = useState( - new QueryClient({ - defaultOptions: { - // react-query ์ „์—ญ ์„ค์ • - queries: { - retry: false, - retryOnMount: true, - refetchOnReconnect: false, - refetchOnWindowFocus: false, - }, - }, - }), - ); - - return ( - - {children} - - - ); -}; diff --git a/services/one-app/src/app/_components/WelcomeMessage.spec.tsx b/services/one-app/src/app/_components/WelcomeMessage.spec.tsx new file mode 100644 index 000000000..683f18062 --- /dev/null +++ b/services/one-app/src/app/_components/WelcomeMessage.spec.tsx @@ -0,0 +1,28 @@ +import { render, screen } from '@testing-library/react'; + +import WelcomeMessage from './WelcomeMessage'; + +describe('WelcomeMessage', () => { + it('renders username correctly', () => { + render(); + expect(screen.getByText('์•„ํ•˜์ฒ ๋‹˜,')).toBeInTheDocument(); + }); + + it('renders a greeting phrase', () => { + render(); + + const greetingPhrases = [ + '๋ฉ‹์ง„ ์˜ค๋Š˜์„ ์‘์›ํ•ด์š”', + '์ •๋ง ์ž˜ํ•˜๊ณ  ์žˆ์–ด์š”', + '์–ธ์ œ๋‚˜ ๋Š˜ ์‘์›ํ• ๊ฒŒ์š”', + '๋งค์ผ๋งค์ผ ๋ฐ˜๊ฐ€์›Œ์š”!', + 'ํž˜์ฐจ๊ฒŒ ์‹œ์ž‘ํ•ด๋ณผ๊นŒ์š”?', + 'ํ™œ์ง ์›ƒ๋Š” ํ•˜๋ฃจ๋˜์„ธ์š”', + 'ํ•˜๋‚˜์”ฉ ์ด๋ค„๊ฐ€๋ณผ๊นŒ์š”?', + '๋ชฉํ‘œ์— ํ•œ ๊ฑธ์Œ ๋‹ค๊ฐ€๊ฐ€์š”!', + ]; + + const greeting = screen.getByText(content => greetingPhrases.includes(content)); + expect(greeting).toBeInTheDocument(); + }); +}); diff --git a/services/one-app/src/app/_components/WelcomeMessage.tsx b/services/one-app/src/app/_components/WelcomeMessage.tsx new file mode 100644 index 000000000..7cc55fd52 --- /dev/null +++ b/services/one-app/src/app/_components/WelcomeMessage.tsx @@ -0,0 +1,29 @@ +const GREETING_PHRASES = [ + '๋ฉ‹์ง„ ์˜ค๋Š˜์„ ์‘์›ํ•ด์š”', + '์ •๋ง ์ž˜ํ•˜๊ณ  ์žˆ์–ด์š”', + '์–ธ์ œ๋‚˜ ๋Š˜ ์‘์›ํ• ๊ฒŒ์š”', + '๋งค์ผ๋งค์ผ ๋ฐ˜๊ฐ€์›Œ์š”!', + 'ํž˜์ฐจ๊ฒŒ ์‹œ์ž‘ํ•ด๋ณผ๊นŒ์š”?', + 'ํ™œ์ง ์›ƒ๋Š” ํ•˜๋ฃจ๋˜์„ธ์š”', + 'ํ•˜๋‚˜์”ฉ ์ด๋ค„๊ฐ€๋ณผ๊นŒ์š”?', + '๋ชฉํ‘œ์— ํ•œ ๊ฑธ์Œ ๋‹ค๊ฐ€๊ฐ€์š”!', +] as const; + +type GreetingPhrase = (typeof GREETING_PHRASES)[number]; + +const getRandomGreeting = (): GreetingPhrase => { + return GREETING_PHRASES[Math.floor(Math.random() * GREETING_PHRASES.length)]; +}; + +const greetingPhrase = getRandomGreeting(); + +const WelcomeMessage = () => { + return ( +

    + {'์•„ํ•˜์ฒ '}๋‹˜, + {greetingPhrase} +

    + ); +}; + +export default WelcomeMessage; diff --git a/services/one-app/src/app/_components/index.ts b/services/one-app/src/app/_components/index.ts deleted file mode 100644 index a7d2112a1..000000000 --- a/services/one-app/src/app/_components/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './RQProvider'; -export * from './MSWComponent'; diff --git a/services/one-app/src/app/api/index.ts b/services/one-app/src/app/api/index.ts deleted file mode 100644 index d6cf84d4b..000000000 --- a/services/one-app/src/app/api/index.ts +++ /dev/null @@ -1,61 +0,0 @@ -import axios, { AxiosError, AxiosInstance, isAxiosError } from 'axios'; - -import { API_BASE_URL } from '@/common/constants'; -import { AuthService } from '@/common/service'; -import type { APIErrorResponse } from '@/model'; - -// TODO, access_token ์„ ํƒ์ ์œผ๋กœ ๋ณด๋‚ด๋Š” ๋ฐฉ์•ˆ ๋ชจ์ƒ‰ (axios type ํ™•์žฅ) -// publicํ•œ ํ•จ์ˆ˜(์ธ๊ธฐ ๊ฒ€์ƒ‰ ์ˆœ์œ„ ๋“ฑ)๋“ค์˜ ๊ฒฝ์šฐ ๊ตณ์ด access_token์ด ์—†์Œ์„ ์ฒ˜๋ฆฌํ•  ํ•„์š”๊ฐ€ ์—†์Œ - -const setInterceptor = (instance: AxiosInstance) => { - instance.interceptors.request.use( - config => { - const requestConfig = config; - - // Access Token ์„ค์ • - const accessToken = AuthService.accessToken; - if (accessToken) { - requestConfig.headers.Authorization = `Bearer ${accessToken}`; - } - - return requestConfig; - }, - (err: AxiosError): Promise => Promise.reject(err), - ); - - // TODO, ์‘๋‹ต ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋‹ค๋“ฌ๊ธฐ - instance.interceptors.response.use( - response => response, - async error => { - if (isAxiosError(error) && error.response?.data) { - const { code } = error.response.data as APIErrorResponse; - - // console.log(code, error.response.data); - - if (code === '202') { - // ์•ก์„ธ์Šค ํ† ํฐ ๋งŒ๋ฃŒ ์‹œ ์žฌ์š”์ฒญ - return AuthService.resetTokenAndRetryRequest(error); - } - - if (['201', '203', '204', '205'].includes(code)) { - // ์„ธ์…˜ ๋งŒ๋ฃŒ ์‹œ ๋กœ๊ทธ์•„์›ƒ ์ฒ˜๋ฆฌ - AuthService.expireSession(); - return Promise.reject(error); - } - } - - console.error(error); - return Promise.reject(error); - }, - ); - - return instance; -}; - -const apiClient = setInterceptor( - axios.create({ - baseURL: API_BASE_URL, - }), -); - -export { apiClient }; diff --git a/services/one-app/src/app/layout.tsx b/services/one-app/src/app/layout.tsx new file mode 100644 index 000000000..9c8aa6d72 --- /dev/null +++ b/services/one-app/src/app/layout.tsx @@ -0,0 +1,62 @@ +import type { Metadata, Viewport } from 'next'; +import NextTopLoader from 'nextjs-toploader'; + +import { Pretendard } from '@/asset/font/pretendard'; +import Providers from '@/context/providers'; +import { cn } from '@/util/cn'; + +import Header from './_components/Header'; +import NavMenu from './_components/NavMenu'; +import './globals.css'; + +export const viewport: Viewport = { + themeColor: 'white', + width: 'device-width', + initialScale: 1, + maximumScale: 1, + userScalable: false, +}; + +export const metadata: Metadata = { + title: '์•„ํ•˜์ฒ  / 1๋“ฑ ์ง€ํ•˜์ฒ  ๋ฏผ์› & ๋ถ„์‹ค๋ฌผ & ์ปค๋ฎค๋‹ˆํ‹ฐ ์ •๋ณด ์•ฑ', + description: + '์ง€ํ•˜์ฒ  ์ด์šฉ์˜ ๋ชจ๋“  ๊ฒƒ, ์•„ํ•˜์ฒ ๊ณผ ํ•จ๊ป˜ํ•˜์„ธ์š”. ๋ถˆํŽธ์‚ฌํ•ญ์€ ๋ฏผ์› ์„œ๋น„์Šค๋กœ ํ•ด๊ฒฐํ•˜๊ณ , ์†Œ์ค‘ํ•œ ๋ถ„์‹ค๋ฌผ์€ ๋น ๋ฅด๊ฒŒ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ง€ํ•˜์ฒ  ์ด์šฉ๊ฐ๋“ค๊ณผ ์ผ์ƒ์„ ๋‚˜๋ˆ„๊ณ  ์œ ์šฉํ•œ ์ •๋ณด๋ฅผ ๊ณต์œ ํ•˜๋Š” ๋”ฐ๋œปํ•œ ์ปค๋ฎค๋‹ˆํ‹ฐ๊นŒ์ง€, ๋” ๋‚˜์€ ์ง€ํ•˜์ฒ  ๋ฌธํ™”๋ฅผ ๋งŒ๋“ค์–ด๊ฐ‘๋‹ˆ๋‹ค.', + applicationName: '์•„ํ•˜์ฒ  | AhHachul', + keywords: [ + '์ง€ํ•˜์ฒ ', + '์ง€ํ•˜์ฒ  ๋ฏผ์›', + '์ง€ํ•˜์ฒ  ๋ถ„์‹ค๋ฌผ', + '์ง€ํ•˜์ฒ  ์œ ์‹ค๋ฌผ', + '1ํ˜ธ์„ ', + '2ํ˜ธ์„ ', + '3ํ˜ธ์„ ', + '4ํ˜ธ์„ ', + '5ํ˜ธ์„ ', + '6ํ˜ธ์„ ', + '7ํ˜ธ์„ ', + '8ํ˜ธ์„ ', + '9ํ˜ธ์„ ', + '์‹ ๋ถ„๋‹น์„ ', + '์ˆ˜์ธ๋ถ„๋‹น์„ ', + '๊ฒฝ์˜์ค‘์•™์„ ', + ].join(', '), +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + + +
    + {children} + + + + + ); +} diff --git a/services/one-app/src/app/not-found.tsx b/services/one-app/src/app/not-found.tsx new file mode 100644 index 000000000..51afe3439 --- /dev/null +++ b/services/one-app/src/app/not-found.tsx @@ -0,0 +1,19 @@ +import type { Metadata } from 'next'; +import Link from 'next/link'; + +export const metadata: Metadata = { + title: + '์š”์ฒญํ•˜์‹  ํŽ˜์ด์ง€๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค / ์•„ํ•˜์ฒ  - 1๋“ฑ ์ง€ํ•˜์ฒ  ๋ฏผ์› & ์œ ์‹ค๋ฌผ & ์ปค๋ฎค๋‹ˆํ‹ฐ ์ •๋ณด ์•ฑ', + description: '์š”์ฒญํ•˜์‹  ํŽ˜์ด์ง€๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค', +}; + +export default function NotFound() { + return ( +
    +
    ์ด ํŽ˜์ด์ง€๋Š” ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ํŽ˜์ด์ง€๋ฅผ ๊ตฌ๊ฒฝํ•ด๋ณด์„ธ์š”.
    + + ํ™ˆ์œผ๋กœ ์ด๋™ + +
    + ); +} diff --git a/services/one-app/src/app/page.tsx b/services/one-app/src/app/page.tsx new file mode 100644 index 000000000..fd43cc9f2 --- /dev/null +++ b/services/one-app/src/app/page.tsx @@ -0,0 +1,30 @@ +import type { Metadata } from 'next'; + +import WelcomeMessage from '@/app/_components/WelcomeMessage'; + +export const metadata: Metadata = { + title: 'ํ™ˆ / 1๋“ฑ ์ง€ํ•˜์ฒ  ๋ฏผ์› & ๋ถ„์‹ค๋ฌผ & ์ปค๋ฎค๋‹ˆํ‹ฐ ์ •๋ณด ์•ฑ - ์•„ํ•˜์ฒ ', + description: + '์ง€ํ•˜์ฒ  ์ด์šฉ์˜ ๋ชจ๋“  ๊ฒƒ, ์•„ํ•˜์ฒ ๊ณผ ํ•จ๊ป˜ํ•˜์„ธ์š”. ๋ถˆํŽธ์‚ฌํ•ญ์€ ๋ฏผ์› ์„œ๋น„์Šค๋กœ ํ•ด๊ฒฐํ•˜๊ณ , ์†Œ์ค‘ํ•œ ๋ถ„์‹ค๋ฌผ์€ ๋น ๋ฅด๊ฒŒ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ง€ํ•˜์ฒ  ์ด์šฉ๊ฐ๋“ค๊ณผ ์ผ์ƒ์„ ๋‚˜๋ˆ„๊ณ  ์œ ์šฉํ•œ ์ •๋ณด๋ฅผ ๊ณต์œ ํ•˜๋Š” ๋”ฐ๋œปํ•œ ์ปค๋ฎค๋‹ˆํ‹ฐ๊นŒ์ง€, ๋” ๋‚˜์€ ์ง€ํ•˜์ฒ  ๋ฌธํ™”๋ฅผ ๋งŒ๋“ค์–ด๊ฐ‘๋‹ˆ๋‹ค.', + applicationName: '์•„ํ•˜์ฒ  | AhHachul', + openGraph: { + title: 'ํ™ˆ / 1๋“ฑ ์ง€ํ•˜์ฒ  ๋ฏผ์› & ๋ถ„์‹ค๋ฌผ & ์ปค๋ฎค๋‹ˆํ‹ฐ ์ •๋ณด ์•ฑ - ์•„ํ•˜์ฒ ', + description: + '์ง€ํ•˜์ฒ  ์ด์šฉ์˜ ๋ชจ๋“  ๊ฒƒ, ์•„ํ•˜์ฒ ๊ณผ ํ•จ๊ป˜ํ•˜์„ธ์š”. ๋ถˆํŽธ์‚ฌํ•ญ์€ ๋ฏผ์› ์„œ๋น„์Šค๋กœ ํ•ด๊ฒฐํ•˜๊ณ , ์†Œ์ค‘ํ•œ ๋ถ„์‹ค๋ฌผ์€ ๋น ๋ฅด๊ฒŒ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ง€ํ•˜์ฒ  ์ด์šฉ๊ฐ๋“ค๊ณผ ์ผ์ƒ์„ ๋‚˜๋ˆ„๊ณ  ์œ ์šฉํ•œ ์ •๋ณด๋ฅผ ๊ณต์œ ํ•˜๋Š” ๋”ฐ๋œปํ•œ ์ปค๋ฎค๋‹ˆํ‹ฐ๊นŒ์ง€, ๋” ๋‚˜์€ ์ง€ํ•˜์ฒ  ๋ฌธํ™”๋ฅผ ๋งŒ๋“ค์–ด๊ฐ‘๋‹ˆ๋‹ค.', + images: [ + { + url: 'https://static.dev.ahhachul.com/banners/main.png', + width: 800, + height: 400, + }, + ], + }, +}; + +export default function Home() { + return ( +
    + +
    + ); +} diff --git a/services/one-app/src/common/assets/fonts/pretendard/index.ts b/services/one-app/src/asset/font/pretendard/index.ts similarity index 100% rename from services/one-app/src/common/assets/fonts/pretendard/index.ts rename to services/one-app/src/asset/font/pretendard/index.ts diff --git a/services/one-app/src/common/assets/fonts/pretendard/pretendard-variable.woff2 b/services/one-app/src/asset/font/pretendard/pretendard-variable.woff2 similarity index 100% rename from services/one-app/src/common/assets/fonts/pretendard/pretendard-variable.woff2 rename to services/one-app/src/asset/font/pretendard/pretendard-variable.woff2 diff --git a/services/one-app/src/common/assets/graphics/character/index.ts b/services/one-app/src/asset/graphic/character/index.ts similarity index 100% rename from services/one-app/src/common/assets/graphics/character/index.ts rename to services/one-app/src/asset/graphic/character/index.ts diff --git a/services/one-app/src/common/assets/graphics/character/no-results.svg b/services/one-app/src/asset/graphic/character/no-results.svg similarity index 100% rename from services/one-app/src/common/assets/graphics/character/no-results.svg rename to services/one-app/src/asset/graphic/character/no-results.svg diff --git a/services/one-app/src/common/assets/graphics/index.ts b/services/one-app/src/asset/graphic/index.ts similarity index 100% rename from services/one-app/src/common/assets/graphics/index.ts rename to services/one-app/src/asset/graphic/index.ts diff --git a/services/one-app/src/common/assets/icons/alert-circle.tsx b/services/one-app/src/asset/icon/alert-circle.tsx similarity index 100% rename from services/one-app/src/common/assets/icons/alert-circle.tsx rename to services/one-app/src/asset/icon/alert-circle.tsx diff --git a/services/one-app/src/common/assets/icons/apple.tsx b/services/one-app/src/asset/icon/apple.tsx similarity index 100% rename from services/one-app/src/common/assets/icons/apple.tsx rename to services/one-app/src/asset/icon/apple.tsx diff --git a/services/one-app/src/common/assets/icons/arrow-down.tsx b/services/one-app/src/asset/icon/arrow-down.tsx similarity index 100% rename from services/one-app/src/common/assets/icons/arrow-down.tsx rename to services/one-app/src/asset/icon/arrow-down.tsx diff --git a/services/one-app/src/common/assets/icons/arrow-left.tsx b/services/one-app/src/asset/icon/arrow-left.tsx similarity index 100% rename from services/one-app/src/common/assets/icons/arrow-left.tsx rename to services/one-app/src/asset/icon/arrow-left.tsx diff --git a/services/one-app/src/common/assets/icons/check.tsx b/services/one-app/src/asset/icon/check.tsx similarity index 100% rename from services/one-app/src/common/assets/icons/check.tsx rename to services/one-app/src/asset/icon/check.tsx diff --git a/services/one-app/src/common/assets/icons/chevron-down.tsx b/services/one-app/src/asset/icon/chevron-down.tsx similarity index 100% rename from services/one-app/src/common/assets/icons/chevron-down.tsx rename to services/one-app/src/asset/icon/chevron-down.tsx diff --git a/services/one-app/src/common/assets/icons/close-circle.tsx b/services/one-app/src/asset/icon/close-circle.tsx similarity index 100% rename from services/one-app/src/common/assets/icons/close-circle.tsx rename to services/one-app/src/asset/icon/close-circle.tsx diff --git a/services/one-app/src/common/assets/icons/comment.tsx b/services/one-app/src/asset/icon/comment.tsx similarity index 100% rename from services/one-app/src/common/assets/icons/comment.tsx rename to services/one-app/src/asset/icon/comment.tsx diff --git a/services/one-app/src/common/assets/icons/google.tsx b/services/one-app/src/asset/icon/google.tsx similarity index 100% rename from services/one-app/src/common/assets/icons/google.tsx rename to services/one-app/src/asset/icon/google.tsx diff --git a/services/one-app/src/common/assets/icons/image-placeholder.tsx b/services/one-app/src/asset/icon/image-placeholder.tsx similarity index 100% rename from services/one-app/src/common/assets/icons/image-placeholder.tsx rename to services/one-app/src/asset/icon/image-placeholder.tsx diff --git a/services/one-app/src/common/assets/icons/index.ts b/services/one-app/src/asset/icon/index.ts similarity index 100% rename from services/one-app/src/common/assets/icons/index.ts rename to services/one-app/src/asset/icon/index.ts diff --git a/services/one-app/src/common/assets/icons/kakao.tsx b/services/one-app/src/asset/icon/kakao.tsx similarity index 100% rename from services/one-app/src/common/assets/icons/kakao.tsx rename to services/one-app/src/asset/icon/kakao.tsx diff --git a/services/one-app/src/common/assets/icons/line/Incheon-one.svg b/services/one-app/src/asset/icon/line/Incheon-one.svg similarity index 100% rename from services/one-app/src/common/assets/icons/line/Incheon-one.svg rename to services/one-app/src/asset/icon/line/Incheon-one.svg diff --git a/services/one-app/src/common/assets/icons/line/Incheon-two.svg b/services/one-app/src/asset/icon/line/Incheon-two.svg similarity index 100% rename from services/one-app/src/common/assets/icons/line/Incheon-two.svg rename to services/one-app/src/asset/icon/line/Incheon-two.svg diff --git a/services/one-app/src/common/assets/icons/line/airport.svg b/services/one-app/src/asset/icon/line/airport.svg similarity index 100% rename from services/one-app/src/common/assets/icons/line/airport.svg rename to services/one-app/src/asset/icon/line/airport.svg diff --git a/services/one-app/src/common/assets/icons/line/eight.svg b/services/one-app/src/asset/icon/line/eight.svg similarity index 100% rename from services/one-app/src/common/assets/icons/line/eight.svg rename to services/one-app/src/asset/icon/line/eight.svg diff --git a/services/one-app/src/common/assets/icons/line/everline.svg b/services/one-app/src/asset/icon/line/everline.svg similarity index 100% rename from services/one-app/src/common/assets/icons/line/everline.svg rename to services/one-app/src/asset/icon/line/everline.svg diff --git a/services/one-app/src/common/assets/icons/line/five.svg b/services/one-app/src/asset/icon/line/five.svg similarity index 100% rename from services/one-app/src/common/assets/icons/line/five.svg rename to services/one-app/src/asset/icon/line/five.svg diff --git a/services/one-app/src/common/assets/icons/line/four.svg b/services/one-app/src/asset/icon/line/four.svg similarity index 100% rename from services/one-app/src/common/assets/icons/line/four.svg rename to services/one-app/src/asset/icon/line/four.svg diff --git a/services/one-app/src/common/assets/icons/line/gimpo-gold.svg b/services/one-app/src/asset/icon/line/gimpo-gold.svg similarity index 100% rename from services/one-app/src/common/assets/icons/line/gimpo-gold.svg rename to services/one-app/src/asset/icon/line/gimpo-gold.svg diff --git a/services/one-app/src/common/assets/icons/line/gyeongchun.svg b/services/one-app/src/asset/icon/line/gyeongchun.svg similarity index 100% rename from services/one-app/src/common/assets/icons/line/gyeongchun.svg rename to services/one-app/src/asset/icon/line/gyeongchun.svg diff --git a/services/one-app/src/common/assets/icons/line/gyeonggang.svg b/services/one-app/src/asset/icon/line/gyeonggang.svg similarity index 100% rename from services/one-app/src/common/assets/icons/line/gyeonggang.svg rename to services/one-app/src/asset/icon/line/gyeonggang.svg diff --git a/services/one-app/src/common/assets/icons/line/gyeongui-jungang.svg b/services/one-app/src/asset/icon/line/gyeongui-jungang.svg similarity index 100% rename from services/one-app/src/common/assets/icons/line/gyeongui-jungang.svg rename to services/one-app/src/asset/icon/line/gyeongui-jungang.svg diff --git a/services/one-app/src/common/assets/icons/line/nine.svg b/services/one-app/src/asset/icon/line/nine.svg similarity index 100% rename from services/one-app/src/common/assets/icons/line/nine.svg rename to services/one-app/src/asset/icon/line/nine.svg diff --git a/services/one-app/src/common/assets/icons/line/one.svg b/services/one-app/src/asset/icon/line/one.svg similarity index 100% rename from services/one-app/src/common/assets/icons/line/one.svg rename to services/one-app/src/asset/icon/line/one.svg diff --git a/services/one-app/src/common/assets/icons/line/seohae.svg b/services/one-app/src/asset/icon/line/seohae.svg similarity index 100% rename from services/one-app/src/common/assets/icons/line/seohae.svg rename to services/one-app/src/asset/icon/line/seohae.svg diff --git a/services/one-app/src/common/assets/icons/line/seven.svg b/services/one-app/src/asset/icon/line/seven.svg similarity index 100% rename from services/one-app/src/common/assets/icons/line/seven.svg rename to services/one-app/src/asset/icon/line/seven.svg diff --git a/services/one-app/src/common/assets/icons/line/shinbundang.svg b/services/one-app/src/asset/icon/line/shinbundang.svg similarity index 100% rename from services/one-app/src/common/assets/icons/line/shinbundang.svg rename to services/one-app/src/asset/icon/line/shinbundang.svg diff --git a/services/one-app/src/common/assets/icons/line/sillim.svg b/services/one-app/src/asset/icon/line/sillim.svg similarity index 100% rename from services/one-app/src/common/assets/icons/line/sillim.svg rename to services/one-app/src/asset/icon/line/sillim.svg diff --git a/services/one-app/src/common/assets/icons/line/six.svg b/services/one-app/src/asset/icon/line/six.svg similarity index 100% rename from services/one-app/src/common/assets/icons/line/six.svg rename to services/one-app/src/asset/icon/line/six.svg diff --git a/services/one-app/src/common/assets/icons/line/suin-bundang.svg b/services/one-app/src/asset/icon/line/suin-bundang.svg similarity index 100% rename from services/one-app/src/common/assets/icons/line/suin-bundang.svg rename to services/one-app/src/asset/icon/line/suin-bundang.svg diff --git a/services/one-app/src/common/assets/icons/line/three.svg b/services/one-app/src/asset/icon/line/three.svg similarity index 100% rename from services/one-app/src/common/assets/icons/line/three.svg rename to services/one-app/src/asset/icon/line/three.svg diff --git a/services/one-app/src/common/assets/icons/line/two.svg b/services/one-app/src/asset/icon/line/two.svg similarity index 100% rename from services/one-app/src/common/assets/icons/line/two.svg rename to services/one-app/src/asset/icon/line/two.svg diff --git a/services/one-app/src/common/assets/icons/line/ui-sinseol.svg b/services/one-app/src/asset/icon/line/ui-sinseol.svg similarity index 100% rename from services/one-app/src/common/assets/icons/line/ui-sinseol.svg rename to services/one-app/src/asset/icon/line/ui-sinseol.svg diff --git a/services/one-app/src/common/assets/icons/line/uijeongbu.svg b/services/one-app/src/asset/icon/line/uijeongbu.svg similarity index 100% rename from services/one-app/src/common/assets/icons/line/uijeongbu.svg rename to services/one-app/src/asset/icon/line/uijeongbu.svg diff --git a/services/one-app/src/common/assets/icons/loading-spinner.tsx b/services/one-app/src/asset/icon/loading-spinner.tsx similarity index 94% rename from services/one-app/src/common/assets/icons/loading-spinner.tsx rename to services/one-app/src/asset/icon/loading-spinner.tsx index 114be99e5..a207d3247 100644 --- a/services/one-app/src/common/assets/icons/loading-spinner.tsx +++ b/services/one-app/src/asset/icon/loading-spinner.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { cn } from '@/common/utils/cn'; +import { cn } from '@/util/cn'; interface SpinnerIconProps extends React.SVGProps { size?: number; diff --git a/services/one-app/src/common/assets/icons/logo-text.tsx b/services/one-app/src/asset/icon/logo-text.tsx similarity index 100% rename from services/one-app/src/common/assets/icons/logo-text.tsx rename to services/one-app/src/asset/icon/logo-text.tsx diff --git a/services/one-app/src/common/assets/icons/logo.tsx b/services/one-app/src/asset/icon/logo.tsx similarity index 100% rename from services/one-app/src/common/assets/icons/logo.tsx rename to services/one-app/src/asset/icon/logo.tsx diff --git a/services/one-app/src/common/assets/icons/mic.svg b/services/one-app/src/asset/icon/mic.svg similarity index 100% rename from services/one-app/src/common/assets/icons/mic.svg rename to services/one-app/src/asset/icon/mic.svg diff --git a/services/one-app/src/common/assets/icons/plus.tsx b/services/one-app/src/asset/icon/plus.tsx similarity index 100% rename from services/one-app/src/common/assets/icons/plus.tsx rename to services/one-app/src/asset/icon/plus.tsx diff --git a/services/one-app/src/common/assets/icons/search.tsx b/services/one-app/src/asset/icon/search.tsx similarity index 100% rename from services/one-app/src/common/assets/icons/search.tsx rename to services/one-app/src/asset/icon/search.tsx diff --git a/services/one-app/src/common/assets/icons/subway-logo/index.ts b/services/one-app/src/asset/icon/subway-logo/index.ts similarity index 100% rename from services/one-app/src/common/assets/icons/subway-logo/index.ts rename to services/one-app/src/asset/icon/subway-logo/index.ts diff --git a/services/one-app/src/common/assets/icons/subway-logo/subway_logo_1.svg b/services/one-app/src/asset/icon/subway-logo/subway_logo_1.svg similarity index 100% rename from services/one-app/src/common/assets/icons/subway-logo/subway_logo_1.svg rename to services/one-app/src/asset/icon/subway-logo/subway_logo_1.svg diff --git a/services/one-app/src/common/assets/icons/subway-logo/subway_logo_2.svg b/services/one-app/src/asset/icon/subway-logo/subway_logo_2.svg similarity index 100% rename from services/one-app/src/common/assets/icons/subway-logo/subway_logo_2.svg rename to services/one-app/src/asset/icon/subway-logo/subway_logo_2.svg diff --git a/services/one-app/src/common/assets/icons/subway-logo/subway_logo_3.svg b/services/one-app/src/asset/icon/subway-logo/subway_logo_3.svg similarity index 100% rename from services/one-app/src/common/assets/icons/subway-logo/subway_logo_3.svg rename to services/one-app/src/asset/icon/subway-logo/subway_logo_3.svg diff --git a/services/one-app/src/common/assets/icons/subway-logo/subway_logo_4.svg b/services/one-app/src/asset/icon/subway-logo/subway_logo_4.svg similarity index 100% rename from services/one-app/src/common/assets/icons/subway-logo/subway_logo_4.svg rename to services/one-app/src/asset/icon/subway-logo/subway_logo_4.svg diff --git a/services/one-app/src/common/assets/icons/subway-logo/subway_logo_5.svg b/services/one-app/src/asset/icon/subway-logo/subway_logo_5.svg similarity index 100% rename from services/one-app/src/common/assets/icons/subway-logo/subway_logo_5.svg rename to services/one-app/src/asset/icon/subway-logo/subway_logo_5.svg diff --git a/services/one-app/src/common/assets/icons/subway-logo/subway_logo_6.svg b/services/one-app/src/asset/icon/subway-logo/subway_logo_6.svg similarity index 100% rename from services/one-app/src/common/assets/icons/subway-logo/subway_logo_6.svg rename to services/one-app/src/asset/icon/subway-logo/subway_logo_6.svg diff --git a/services/one-app/src/common/assets/icons/subway-logo/subway_logo_7.svg b/services/one-app/src/asset/icon/subway-logo/subway_logo_7.svg similarity index 100% rename from services/one-app/src/common/assets/icons/subway-logo/subway_logo_7.svg rename to services/one-app/src/asset/icon/subway-logo/subway_logo_7.svg diff --git a/services/one-app/src/common/assets/icons/subway-logo/subway_logo_8.svg b/services/one-app/src/asset/icon/subway-logo/subway_logo_8.svg similarity index 100% rename from services/one-app/src/common/assets/icons/subway-logo/subway_logo_8.svg rename to services/one-app/src/asset/icon/subway-logo/subway_logo_8.svg diff --git a/services/one-app/src/common/assets/icons/subway-logo/subway_logo_9.svg b/services/one-app/src/asset/icon/subway-logo/subway_logo_9.svg similarity index 100% rename from services/one-app/src/common/assets/icons/subway-logo/subway_logo_9.svg rename to services/one-app/src/asset/icon/subway-logo/subway_logo_9.svg diff --git a/services/one-app/src/common/assets/icons/subway-logo/subway_logo_GTX_A.svg b/services/one-app/src/asset/icon/subway-logo/subway_logo_GTX_A.svg similarity index 100% rename from services/one-app/src/common/assets/icons/subway-logo/subway_logo_GTX_A.svg rename to services/one-app/src/asset/icon/subway-logo/subway_logo_GTX_A.svg diff --git "a/services/one-app/src/common/assets/icons/subway-logo/subway_logo_\352\262\275\352\260\225.svg" "b/services/one-app/src/asset/icon/subway-logo/subway_logo_\352\262\275\352\260\225.svg" similarity index 100% rename from "services/one-app/src/common/assets/icons/subway-logo/subway_logo_\352\262\275\352\260\225.svg" rename to "services/one-app/src/asset/icon/subway-logo/subway_logo_\352\262\275\352\260\225.svg" diff --git "a/services/one-app/src/common/assets/icons/subway-logo/subway_logo_\352\262\275\354\235\230\354\244\221\354\225\231.svg" "b/services/one-app/src/asset/icon/subway-logo/subway_logo_\352\262\275\354\235\230\354\244\221\354\225\231.svg" similarity index 100% rename from "services/one-app/src/common/assets/icons/subway-logo/subway_logo_\352\262\275\354\235\230\354\244\221\354\225\231.svg" rename to "services/one-app/src/asset/icon/subway-logo/subway_logo_\352\262\275\354\235\230\354\244\221\354\225\231.svg" diff --git "a/services/one-app/src/common/assets/icons/subway-logo/subway_logo_\352\262\275\354\266\230.svg" "b/services/one-app/src/asset/icon/subway-logo/subway_logo_\352\262\275\354\266\230.svg" similarity index 100% rename from "services/one-app/src/common/assets/icons/subway-logo/subway_logo_\352\262\275\354\266\230.svg" rename to "services/one-app/src/asset/icon/subway-logo/subway_logo_\352\262\275\354\266\230.svg" diff --git "a/services/one-app/src/common/assets/icons/subway-logo/subway_logo_\352\263\265\355\225\255.svg" "b/services/one-app/src/asset/icon/subway-logo/subway_logo_\352\263\265\355\225\255.svg" similarity index 100% rename from "services/one-app/src/common/assets/icons/subway-logo/subway_logo_\352\263\265\355\225\255.svg" rename to "services/one-app/src/asset/icon/subway-logo/subway_logo_\352\263\265\355\225\255.svg" diff --git "a/services/one-app/src/common/assets/icons/subway-logo/subway_logo_\352\271\200\355\217\254\352\263\250\353\223\234.svg" "b/services/one-app/src/asset/icon/subway-logo/subway_logo_\352\271\200\355\217\254\352\263\250\353\223\234.svg" similarity index 100% rename from "services/one-app/src/common/assets/icons/subway-logo/subway_logo_\352\271\200\355\217\254\352\263\250\353\223\234.svg" rename to "services/one-app/src/asset/icon/subway-logo/subway_logo_\352\271\200\355\217\254\352\263\250\353\223\234.svg" diff --git "a/services/one-app/src/common/assets/icons/subway-logo/subway_logo_\354\204\234\355\225\264.svg" "b/services/one-app/src/asset/icon/subway-logo/subway_logo_\354\204\234\355\225\264.svg" similarity index 100% rename from "services/one-app/src/common/assets/icons/subway-logo/subway_logo_\354\204\234\355\225\264.svg" rename to "services/one-app/src/asset/icon/subway-logo/subway_logo_\354\204\234\355\225\264.svg" diff --git "a/services/one-app/src/common/assets/icons/subway-logo/subway_logo_\354\210\230\354\235\270\353\266\204\353\213\271.svg" "b/services/one-app/src/asset/icon/subway-logo/subway_logo_\354\210\230\354\235\270\353\266\204\353\213\271.svg" similarity index 100% rename from "services/one-app/src/common/assets/icons/subway-logo/subway_logo_\354\210\230\354\235\270\353\266\204\353\213\271.svg" rename to "services/one-app/src/asset/icon/subway-logo/subway_logo_\354\210\230\354\235\270\353\266\204\353\213\271.svg" diff --git "a/services/one-app/src/common/assets/icons/subway-logo/subway_logo_\354\213\240\353\246\274.svg" "b/services/one-app/src/asset/icon/subway-logo/subway_logo_\354\213\240\353\246\274.svg" similarity index 100% rename from "services/one-app/src/common/assets/icons/subway-logo/subway_logo_\354\213\240\353\246\274.svg" rename to "services/one-app/src/asset/icon/subway-logo/subway_logo_\354\213\240\353\246\274.svg" diff --git "a/services/one-app/src/common/assets/icons/subway-logo/subway_logo_\354\213\240\353\266\204\353\213\271.svg" "b/services/one-app/src/asset/icon/subway-logo/subway_logo_\354\213\240\353\266\204\353\213\271.svg" similarity index 100% rename from "services/one-app/src/common/assets/icons/subway-logo/subway_logo_\354\213\240\353\266\204\353\213\271.svg" rename to "services/one-app/src/asset/icon/subway-logo/subway_logo_\354\213\240\353\266\204\353\213\271.svg" diff --git "a/services/one-app/src/common/assets/icons/subway-logo/subway_logo_\354\227\220\353\262\204\353\235\274\354\235\270.svg" "b/services/one-app/src/asset/icon/subway-logo/subway_logo_\354\227\220\353\262\204\353\235\274\354\235\270.svg" similarity index 100% rename from "services/one-app/src/common/assets/icons/subway-logo/subway_logo_\354\227\220\353\262\204\353\235\274\354\235\270.svg" rename to "services/one-app/src/asset/icon/subway-logo/subway_logo_\354\227\220\353\262\204\353\235\274\354\235\270.svg" diff --git "a/services/one-app/src/common/assets/icons/subway-logo/subway_logo_\354\232\260\354\235\264\354\213\240\354\204\244.svg" "b/services/one-app/src/asset/icon/subway-logo/subway_logo_\354\232\260\354\235\264\354\213\240\354\204\244.svg" similarity index 100% rename from "services/one-app/src/common/assets/icons/subway-logo/subway_logo_\354\232\260\354\235\264\354\213\240\354\204\244.svg" rename to "services/one-app/src/asset/icon/subway-logo/subway_logo_\354\232\260\354\235\264\354\213\240\354\204\244.svg" diff --git "a/services/one-app/src/common/assets/icons/subway-logo/subway_logo_\354\235\230\354\240\225\353\266\200.svg" "b/services/one-app/src/asset/icon/subway-logo/subway_logo_\354\235\230\354\240\225\353\266\200.svg" similarity index 100% rename from "services/one-app/src/common/assets/icons/subway-logo/subway_logo_\354\235\230\354\240\225\353\266\200.svg" rename to "services/one-app/src/asset/icon/subway-logo/subway_logo_\354\235\230\354\240\225\353\266\200.svg" diff --git "a/services/one-app/src/common/assets/icons/subway-logo/subway_logo_\354\235\270\354\262\234_1.svg" "b/services/one-app/src/asset/icon/subway-logo/subway_logo_\354\235\270\354\262\234_1.svg" similarity index 100% rename from "services/one-app/src/common/assets/icons/subway-logo/subway_logo_\354\235\270\354\262\234_1.svg" rename to "services/one-app/src/asset/icon/subway-logo/subway_logo_\354\235\270\354\262\234_1.svg" diff --git "a/services/one-app/src/common/assets/icons/subway-logo/subway_logo_\354\235\270\354\262\234_2.svg" "b/services/one-app/src/asset/icon/subway-logo/subway_logo_\354\235\270\354\262\234_2.svg" similarity index 100% rename from "services/one-app/src/common/assets/icons/subway-logo/subway_logo_\354\235\270\354\262\234_2.svg" rename to "services/one-app/src/asset/icon/subway-logo/subway_logo_\354\235\270\354\262\234_2.svg" diff --git a/services/one-app/src/common/assets/icons/svgs/bookmark.svg b/services/one-app/src/asset/icon/svgs/bookmark.svg similarity index 100% rename from services/one-app/src/common/assets/icons/svgs/bookmark.svg rename to services/one-app/src/asset/icon/svgs/bookmark.svg diff --git a/services/one-app/src/common/assets/icons/svgs/chevron.svg b/services/one-app/src/asset/icon/svgs/chevron.svg similarity index 100% rename from services/one-app/src/common/assets/icons/svgs/chevron.svg rename to services/one-app/src/asset/icon/svgs/chevron.svg diff --git a/services/one-app/src/common/assets/icons/svgs/dot.svg b/services/one-app/src/asset/icon/svgs/dot.svg similarity index 100% rename from services/one-app/src/common/assets/icons/svgs/dot.svg rename to services/one-app/src/asset/icon/svgs/dot.svg diff --git a/services/one-app/src/common/assets/icons/svgs/ellipsis.svg b/services/one-app/src/asset/icon/svgs/ellipsis.svg similarity index 100% rename from services/one-app/src/common/assets/icons/svgs/ellipsis.svg rename to services/one-app/src/asset/icon/svgs/ellipsis.svg diff --git a/services/one-app/src/common/assets/icons/svgs/index.ts b/services/one-app/src/asset/icon/svgs/index.ts similarity index 100% rename from services/one-app/src/common/assets/icons/svgs/index.ts rename to services/one-app/src/asset/icon/svgs/index.ts diff --git a/services/one-app/src/common/assets/icons/svgs/mic.svg b/services/one-app/src/asset/icon/svgs/mic.svg similarity index 100% rename from services/one-app/src/common/assets/icons/svgs/mic.svg rename to services/one-app/src/asset/icon/svgs/mic.svg diff --git a/services/one-app/src/common/assets/lottie/loading.json b/services/one-app/src/asset/lottie/loading.json similarity index 100% rename from services/one-app/src/common/assets/lottie/loading.json rename to services/one-app/src/asset/lottie/loading.json diff --git a/services/one-app/src/common/components/Filter/SearchFilter.tsx b/services/one-app/src/common/components/Filter/SearchFilter.tsx deleted file mode 100644 index b8804bd68..000000000 --- a/services/one-app/src/common/components/Filter/SearchFilter.tsx +++ /dev/null @@ -1,44 +0,0 @@ -'use client'; - -import React, { useState, useEffect } from 'react'; - -import { useQueryState } from 'nuqs'; - -import { SearchIcon } from '@/common/assets/icons'; -import { useDebounce } from '@/common/hooks'; - -export const SearchFilter = () => { - const [keyword, setKeyword] = useQueryState('keyword', { - scroll: true, - defaultValue: '', - }); - const [inputValue, setInputValue] = useState(keyword); - - const debouncedSetKeyword = useDebounce((value: string) => { - setKeyword(value || null); - }, 300); - - const handleChange = (e: React.ChangeEvent) => { - const value = e.target.value; - setInputValue(value); - debouncedSetKeyword(value); - }; - - useEffect(() => { - setInputValue(keyword); - }, [keyword]); - - return ( -
    - - -
    - ); -}; diff --git a/services/one-app/src/common/components/Subway/index.ts b/services/one-app/src/common/components/Subway/index.ts deleted file mode 100644 index ca4946655..000000000 --- a/services/one-app/src/common/components/Subway/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './subway-logo-icon-map'; diff --git a/services/one-app/src/common/configure-axios.ts b/services/one-app/src/common/configure-axios.ts deleted file mode 100644 index c241835d5..000000000 --- a/services/one-app/src/common/configure-axios.ts +++ /dev/null @@ -1,17 +0,0 @@ -import axios, { AxiosRequestConfig } from 'axios'; - -const config: AxiosRequestConfig = {}; -const baseURL = - process.env.NEXT_PUBLIC_API_MOCKING === 'enabled' - ? 'http://localhost:9090' - : process.env.NEXT_PUBLIC_BASE_URL; -config.baseURL = baseURL; - -const API_BASE = axios.create(config); -enum API_ROUTES { - AUTH = '/auth', - MEMBER = '/members', - COMMUNITY = '/community-posts', -} - -export { API_BASE, API_ROUTES }; diff --git a/services/one-app/src/common/constants/colors.ts b/services/one-app/src/common/constants/colors.ts deleted file mode 100644 index 5869dd76b..000000000 --- a/services/one-app/src/common/constants/colors.ts +++ /dev/null @@ -1,65 +0,0 @@ -module.exports = { - primary: { - primary: '#00BAF6', - primary_pressed: '#009ED1', - primary_hover: '#80DDFB', - }, - secondary: { - secondary: '#D0EEFF', - secondary_pressed: '#B8E5FF', - secondary_hover: '#E2F5FF', - }, - subway: { - s1: '#2A3E91', - s2: '#60B157', - s3: '#FE8A39', - s4: '#509DD8', - s5: '#7F41D8', - s6: '#A95523', - s7: '#727719', - s8: '#D2386E', - s9: '#D1A946', - airport: '#82B5E0', // ๊ณตํ•ญ - gyeongui: '#8CC2A7', // ๊ฒฝ์˜์ค‘์•™ - gyeongchun: '#4FAC7F', // ๊ฒฝ์ถ˜ - suinBundang: '#E1AB3A', // ์ˆ˜์ธ๋ถ„๋‹น - sinBundang: '#BC2A38', // ์‹ ๋ถ„๋‹น - gyeonggang: '#3C74EA', // ๊ฒฝ๊ฐ• - seohae: '#98C255', // ์„œํ•ด - incheon1: '#7899CB', // ์ธ์ฒœ1 - incheon2: '#E9AD54', // ์ธ์ฒœ2 - everline: '#89C07A', // ์—๋ฒ„๋ผ์ธ - uijeongbu: '#C5C03E', // ์˜์ •๋ถ€ - wuisinseol: '#8CC2A7', // ์šฐ์ด์‹ ์„ค - gimpoGold: '#907227', // ๊น€ํฌ๊ณจ๋“œ - sinlim: '#5367A0', // ์‹ ๋ฆผ - }, - gray: { - 0: '#FFFFFF', - 10: '#FCFCFC', - 20: '#F5F5F5', - 30: '#EAECF1', - 40: '#DCDEE7', - 50: '#CED0DD', - 60: '#B7B9C3', - 70: '#95979F', - 80: '#74757C', - 90: '#33333E', - 100: '#121212', - }, - green: { - 50: '#eafaf0', - 100: '#bdf0d1', - 200: '#9de9bb', - 300: '#70df9d', - 400: '#55d989', - 500: '#2acf6c', - 600: '#26bc62', - 700: '#1e934d', - 800: '#17723b', - 900: '#12572d', - }, - 'key-color': '#2ACF6C', - red: '#EB4D3D', - black: '#272727', -}; diff --git a/services/one-app/src/common/constants/env.ts b/services/one-app/src/common/constants/env.ts deleted file mode 100644 index 044ce6b29..000000000 --- a/services/one-app/src/common/constants/env.ts +++ /dev/null @@ -1,23 +0,0 @@ -export enum AppEnv { - PRODUCTION = 'production', - STAGING = 'staging', - DEV = 'development', -} - -export const APP_ENV = process.env.NEXT_PUBLIC_APP_ENV || AppEnv.DEV; -export const IS_DEV_ENV = APP_ENV === AppEnv.DEV; - -export const SITE_URL = (() => { - if (IS_DEV_ENV) return 'http://localhost:3000'; - if (APP_ENV === AppEnv.DEV) return process.env.APP_DEV_URL; - return process.env.APP_PRODUCTION_URL; -})(); - -export const API_BASE_URL = - process.env.NEXT_PUBLIC_API_MOCKING === 'enabled' - ? 'http://localhost:9090' - : IS_DEV_ENV - ? 'https://api.dev.ahhachul.com/v1' - : 'https://api.ahhachul.com/v1'; - -export const IS_SERVER = typeof window === 'undefined'; diff --git a/services/one-app/src/common/constants/fallback-action.ts b/services/one-app/src/common/constants/fallback-action.ts deleted file mode 100644 index eaa93afdb..000000000 --- a/services/one-app/src/common/constants/fallback-action.ts +++ /dev/null @@ -1 +0,0 @@ -export const FALLBACK_ACTION_CLASS_NAME = '-ahhachul-fallback-action'; diff --git a/services/one-app/src/common/constants/subway.ts b/services/one-app/src/common/constants/subway.ts deleted file mode 100644 index 2af091841..000000000 --- a/services/one-app/src/common/constants/subway.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { SubwayLineFilterOptions } from '@/model'; - -export const subwayLineIdOptions = { - [SubwayLineFilterOptions.ALL_LINES]: '์ „์ฒด ํ˜ธ์„  ๋ณด๊ธฐ', - [SubwayLineFilterOptions.ONLY_MY_LINE]: '๋‚ด ํ˜ธ์„ ๋งŒ ๋ณด๊ธฐ', -} as const; diff --git a/services/one-app/src/common/service/AuthService.ts b/services/one-app/src/common/service/AuthService.ts deleted file mode 100644 index 196c51465..000000000 --- a/services/one-app/src/common/service/AuthService.ts +++ /dev/null @@ -1,112 +0,0 @@ -import axios, { AxiosError } from 'axios'; -import Cookies from 'js-cookie'; - -import { apiClient } from '@/app/api'; -import { IS_DEV_ENV } from '@/common/constants'; -import { CookieKey } from '@/model'; - -class _AuthService { - private isFetchingAccessToken = false; - private tokenSubscribers: Array<(accessToken: string) => void> = []; - - constructor() {} - - get accessToken() { - return Cookies.get(CookieKey.ACCESS_TOKEN); - } - - get refreshToken() { - return Cookies.get(CookieKey.REFRESH_TOKEN); - } - - setToken(accessToken: string, refreshToken: string) { - Cookies.set(CookieKey.ACCESS_TOKEN, accessToken, { - sameSite: 'lax', // CSRF ๋ฐฉ์ง€ - secure: !IS_DEV_ENV, - path: '/', // ๋ชจ๋“  ๊ฒฝ๋กœ์—์„œ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๋„๋ก ์„ค์ • - }); - Cookies.set(CookieKey.REFRESH_TOKEN, refreshToken, { - sameSite: 'lax', // CSRF ๋ฐฉ์ง€ - secure: !IS_DEV_ENV, - path: '/', // ๋ชจ๋“  ๊ฒฝ๋กœ์—์„œ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๋„๋ก ์„ค์ • - }); - } - - expireSession() { - Cookies.remove(CookieKey.ACCESS_TOKEN); - Cookies.remove(CookieKey.REFRESH_TOKEN); - window.location.replace('/login'); // TODO, ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๊ฒฝ๋กœ ์„ค์ • - } - - get isLoggedIn() { - return !!this.accessToken && !!this.refreshToken; - } - - async renewAccessToken() { - if (this.isFetchingAccessToken) return; - - if (!this.refreshToken) { - throw new Error('Refresh token is missing'); - } - - try { - this.isFetchingAccessToken = true; - const response = await apiClient.post(`/auth/token/refresh`, { - refreshToken: this.refreshToken, - }); - const { accessToken, refreshToken } = response.data; - this.setToken(accessToken, refreshToken); - - this.broadcastTokenUpdate(accessToken); - } catch (error) { - this.expireSession(); - throw error; - } finally { - this.isFetchingAccessToken = false; - } - } - - async resetTokenAndRetryRequest(error: AxiosError): Promise { - if (!this.refreshToken) { - this.expireSession(); - return Promise.reject(error); - } - - const retryRequest = new Promise((resolve, reject) => { - this.addSubscriber((newAccessToken: string) => { - if (error.config) { - error.config.headers = error.config.headers || {}; - error.config.headers['Authorization'] = `Bearer ${newAccessToken}`; - - // axios๋ฅผ ์‚ฌ์šฉํ•ด ์›๋ž˜ ์š”์ฒญ์„ ๋‹ค์‹œ ์‹œ๋„ - resolve(axios(error.config)); - } else { - // config๊ฐ€ ์—†์œผ๋ฉด reject๋กœ ์˜ค๋ฅ˜ ๋ฐ˜ํ™˜ - reject(new Error('Request configuration is missing')); - } - }); - }); - - if (!this.isFetchingAccessToken) { - this.isFetchingAccessToken = true; - try { - await this.renewAccessToken(); - } catch (err) { - return Promise.reject(err); - } - } - - return retryRequest; - } - - private broadcastTokenUpdate(newAccessToken: string) { - this.tokenSubscribers.forEach(callback => callback(newAccessToken)); - this.tokenSubscribers = []; - } - - private addSubscriber(callback: (accessToken: string) => void) { - this.tokenSubscribers.push(callback); - } -} - -export const AuthService = new _AuthService(); diff --git a/services/one-app/src/common/service/index.ts b/services/one-app/src/common/service/index.ts deleted file mode 100644 index af1ad50c5..000000000 --- a/services/one-app/src/common/service/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './AuthService'; diff --git a/services/one-app/src/common/utils/date.ts b/services/one-app/src/common/utils/date.ts deleted file mode 100644 index 6f9bb4f77..000000000 --- a/services/one-app/src/common/utils/date.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { format, formatDistanceToNow, isValid } from 'date-fns'; -import { ko } from 'date-fns/locale'; - -/** - * @description ์ดˆ๋ฅผ MM:SS ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๋ฉ”์„œ๋“œ์ž…๋‹ˆ๋‹ค. - */ -export const formatMMSS = (totalSeconds: number) => { - const minutes = Math.floor(totalSeconds / 60); - const seconds = totalSeconds % 60; - - return { - minutes, - seconds, - formattedMinutes: String(minutes).padStart(2, '0'), - formattedSeconds: String(seconds).padStart(2, '0'), - }; -}; - -/** - * @description ๋‚ ์งœ๋ฅผ 'MM.DD HH:mm' ํ˜•์‹์œผ๋กœ ํฌ๋งทํŒ…ํ•˜๋Š” ๋ฉ”์„œ๋“œ์ž…๋‹ˆ๋‹ค. - */ -export const formatToStandardDate = (dateString: string): string => { - return format(new Date(dateString), 'MM.dd HH:mm'); -}; - -/** - * @description ๋‚ ์งœ๋ฅผ ์ƒํ™ฉ์— ๋งž๋Š” ํ˜•์‹์œผ๋กœ ํฌ๋งทํŒ…ํ•˜๋Š” ๋ฉ”์„œ๋“œ์ž…๋‹ˆ๋‹ค. - * - 1๋ถ„ ์ด๋‚ด: '๋ฐฉ๊ธˆ ์ „' - * - 24์‹œ๊ฐ„ ์ด๋‚ด: 'n์‹œ๊ฐ„ ์ „' - * - 24์‹œ๊ฐ„ ์ดํ›„: 'MM.DD HH:mm' ๋˜๋Š” 'n์ผ ์ „' - */ -export function formatDate(date: string, shouldShowStandardDate = true) { - const d = new Date(date); - - if (!isValid(d)) { - return '์•Œ ์ˆ˜ ์—†์Œ'; - } - - const now = Date.now(); - const diffInSeconds = (now - d.getTime()) / 1000; - - // 1๋ถ„ ์ด๋‚ด - if (diffInSeconds < 60) { - return '๋ฐฉ๊ธˆ ์ „'; - } - - // 24์‹œ๊ฐ„ ์ด๋‚ด - if (diffInSeconds < 60 * 60 * 24) { - return formatDistanceToNow(d, { addSuffix: true, locale: ko }); - } - - // 24์‹œ๊ฐ„ ์ดํ›„ - return shouldShowStandardDate - ? formatToStandardDate(date) - : formatDistanceToNow(d, { addSuffix: true, locale: ko }); -} diff --git a/services/one-app/src/common/utils/index.ts b/services/one-app/src/common/utils/index.ts deleted file mode 100644 index 305faf813..000000000 --- a/services/one-app/src/common/utils/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export * from './cn'; -export * from './date'; -export * from './array'; -export * from './number'; -export * from './common'; -export * from './object'; -export * from './subway'; -export * from './validate'; -export * from './react-query'; diff --git a/services/one-app/src/common/utils/number.ts b/services/one-app/src/common/utils/number.ts deleted file mode 100644 index 826926c72..000000000 --- a/services/one-app/src/common/utils/number.ts +++ /dev/null @@ -1,5 +0,0 @@ -export function getRandomInt(min = 1, max = 10) { - min = Math.ceil(min); - max = Math.floor(max); - return Math.floor(Math.random() * (max - min + 1)) + min; -} diff --git a/services/one-app/src/common/utils/object.ts b/services/one-app/src/common/utils/object.ts deleted file mode 100644 index 1d8c5882a..000000000 --- a/services/one-app/src/common/utils/object.ts +++ /dev/null @@ -1,45 +0,0 @@ -import queryString from 'query-string'; - -import type { ObjectKeys } from '@/model'; - -export const isValidObject = (obj: unknown): obj is Record => { - return typeof obj === 'object' && obj !== null && !Array.isArray(obj); -}; - -export function removeFalsyValues>( - obj: T, - options: { removeEmptyStrings?: boolean; removeZero?: boolean } = {}, -): Partial { - if (!isValidObject(obj)) { - throw new Error('obj must be a non-null object'); - } - - return Object.entries(obj).reduce((result, [key, value]) => { - if ( - value !== undefined && - value !== null && - (options.removeZero ? value !== 0 : true) && - (options.removeEmptyStrings ? value !== '' : true) - ) { - result[key as keyof T] = value; - } - return result; - }, {} as Partial); -} - -export const objectToQueryString = >( - params: T, - options?: { removeEmptyStrings?: boolean; removeZero?: boolean }, -): string => { - if (!isValidObject(params)) { - throw new Error('params must be a non-null object'); - } - - return queryString.stringify(removeFalsyValues(params, options)); -}; - -export function objectEntries>( - obj: Type, -): Array<[ObjectKeys, Type[ObjectKeys]]> { - return Object.entries(obj) as Array<[ObjectKeys, Type[ObjectKeys]]>; -} diff --git a/services/one-app/src/common/utils/react-query.ts b/services/one-app/src/common/utils/react-query.ts deleted file mode 100644 index d55e2c86d..000000000 --- a/services/one-app/src/common/utils/react-query.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { InfiniteData } from '@tanstack/react-query'; -import { AxiosResponse } from 'axios'; - -import type { IResponse, ListResponseWithPagination } from '@/model'; - -export const generateQueryKey = (type: string[]) => { - return { - all: type, - lists() { - return [...this.all, 'list'] as const; - }, - list(filters: Record) { - return [...this.lists(), filters] as const; - }, - details() { - return [...this.all, 'detail'] as const; - }, - detail(id: number | string) { - return [...this.details(), id] as const; - }, - comments(id: number | string) { - return [...this.detail(id), 'comments'] as const; - }, - }; -}; - -export const flattenInfinityList = ( - data: InfiniteData>>, unknown>, -): TData[] => { - return data.pages.map(page => page?.data?.result?.data ?? []).flat(); -}; diff --git a/services/one-app/src/common/utils/subway.ts b/services/one-app/src/common/utils/subway.ts deleted file mode 100644 index 324865d62..000000000 --- a/services/one-app/src/common/utils/subway.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const formatSubwayLineId = (filtersSubwayLineId: string, userFavoriteLineId: number) => { - switch (filtersSubwayLineId) { - case 'ALL_LINES': - return 0; - case 'ONLY_MY_LINE': - default: - return userFavoriteLineId; - } -}; diff --git a/services/one-app/src/common/utils/validate.ts b/services/one-app/src/common/utils/validate.ts deleted file mode 100644 index 00d047ffc..000000000 --- a/services/one-app/src/common/utils/validate.ts +++ /dev/null @@ -1,29 +0,0 @@ -interface LexicalNode { - root: { - children: unknown[]; - [key: string]: unknown; - }; - [key: string]: unknown; -} - -export function isLexicalContent(content: unknown): content is LexicalNode { - if (typeof content !== 'string') return false; - - try { - const parsed = JSON.parse(content); - - if (!parsed || typeof parsed !== 'object') return false; - - const { root } = parsed; - - if (!root || typeof root !== 'object') return false; - - const { children } = root; - - if (!children || !Array.isArray(children)) return false; - - return true; - } catch { - return false; - } -} diff --git a/services/one-app/src/common/components/Article/ArticleCard.tsx b/services/one-app/src/component/Article/ArticleCard.tsx similarity index 63% rename from services/one-app/src/common/components/Article/ArticleCard.tsx rename to services/one-app/src/component/Article/ArticleCard.tsx index 982f15637..29968feaf 100644 --- a/services/one-app/src/common/components/Article/ArticleCard.tsx +++ b/services/one-app/src/component/Article/ArticleCard.tsx @@ -1,26 +1,28 @@ 'use client'; -import { LexicalSyntaxContentParser } from '@/app/(site)/_component/Editor'; -import { DotIcon, CommentIcon } from '@/common/assets/icons'; -import { SUBWAY_LOGO_SVG_LIST } from '@/common/components'; -import { cn, isLexicalContent, formatDate } from '@/common/utils'; -import type { Post } from '@/model'; +import { LazyLoadImage } from 'react-lazy-load-image-component'; +import 'react-lazy-load-image-component/src/effects/opacity.css'; + +import { formatDateTime } from '@ahhachul/utils'; + +import type { IPost } from '@/types'; +import { cn, isLexicalContent } from '@/util'; + +import { ReadonlyEditor } from '../Editor'; interface Props { - post: Post; + post: IPost; } -export const ArticleCard = ({ post }: Props) => { - const isLexicalSyntaxContent = isLexicalContent(post.content); - +export const Post = ({ post }: Props) => { return (
    {post.title}
    - {isLexicalSyntaxContent ? ( - {
    {post?.imageUrl && (
    - {/* TODO */} - {/* */} + />
    )}
    - {SUBWAY_LOGO_SVG_LIST[post.subwayLineId]} - - {post.writer || 'LOST112'} - - {formatDate(post.createdAt, false)} + {/* {SUBWAY_LOGO_SVG_LIST[post.subwayLineId]} */} + {/* */} + {post.writer || '๋กœ์ŠคํŠธ 112'} + {/* */} + {formatDateTime(post.createdAt)}
    - + {/* */} {post.commentCnt}
    diff --git a/services/one-app/src/common/components/Article/ArticleDetail.suspense.tsx b/services/one-app/src/component/Article/ArticleDetail.suspense.tsx similarity index 97% rename from services/one-app/src/common/components/Article/ArticleDetail.suspense.tsx rename to services/one-app/src/component/Article/ArticleDetail.suspense.tsx index 259fb94bb..98dee43cd 100644 --- a/services/one-app/src/common/components/Article/ArticleDetail.suspense.tsx +++ b/services/one-app/src/component/Article/ArticleDetail.suspense.tsx @@ -1,7 +1,7 @@ 'use client'; -import { useIsDeferred } from '@/common/hooks'; -import { cn } from '@/common/utils'; +import { useIsDeferred } from '@/hook'; +import { cn } from '@/util'; import { BaseSkeleton } from '../BaseSkeleton'; diff --git a/services/one-app/src/common/components/Article/ArticleImages.tsx b/services/one-app/src/component/Article/ArticleImages.tsx similarity index 92% rename from services/one-app/src/common/components/Article/ArticleImages.tsx rename to services/one-app/src/component/Article/ArticleImages.tsx index 41d3c8965..0accc8887 100644 --- a/services/one-app/src/common/components/Article/ArticleImages.tsx +++ b/services/one-app/src/component/Article/ArticleImages.tsx @@ -4,11 +4,11 @@ import { Pagination } from 'swiper/modules'; import { Swiper, SwiperSlide } from 'swiper/react'; // import { LazyLoadImage } from 'react-lazy-load-image-component'; -import type { PostImage } from '@/model'; +import type { IPostImage } from '@/types'; interface Props { label: string; - images: PostImage[]; + images: IPostImage[]; canShowFullImageDialog?: boolean; } @@ -32,7 +32,7 @@ export const BaseArticleImages = ({ return ( <> - {images.length === 1 ? ( + {images?.length === 1 ? (
    */}
    - ) : images.length > 1 ? ( + ) : images && images.length > 1 ? ( - {images.map(img => ( + {images?.map(img => ( { - const { isDeferred } = useIsDeferred(500); - - return isDeferred ? ( + return (
    {
    {
    - {/* ์ด๋ฏธ์ง€ ์Šค์ผˆ๋ ˆํ†ค - ํ•„์š”ํ•œ ๊ฒฝ์šฐ ํ™œ์„ฑํ™” */}
    @@ -56,5 +52,5 @@ export const ArticleListSuspenseFallback = () => {
    ))}
    - ) : null; + ); }; diff --git a/services/one-app/src/common/components/Article/EmptyArticleList.tsx b/services/one-app/src/component/Article/EmptyArticleList.tsx similarity index 81% rename from services/one-app/src/common/components/Article/EmptyArticleList.tsx rename to services/one-app/src/component/Article/EmptyArticleList.tsx index 391814ef5..648376823 100644 --- a/services/one-app/src/common/components/Article/EmptyArticleList.tsx +++ b/services/one-app/src/component/Article/EmptyArticleList.tsx @@ -1,4 +1,4 @@ -import { EmptyGraphic } from '@/common/assets/graphics'; +import { EmptyGraphic } from '@/asset/graphic'; export const EmptyArticleList = () => { return ( diff --git a/services/one-app/src/common/components/Article/FullImageViewer.tsx b/services/one-app/src/component/Article/FullImageViewer.tsx similarity index 100% rename from services/one-app/src/common/components/Article/FullImageViewer.tsx rename to services/one-app/src/component/Article/FullImageViewer.tsx diff --git a/services/one-app/src/common/components/Article/RecommendArticleCard.tsx b/services/one-app/src/component/Article/RecommendArticleCard.tsx similarity index 82% rename from services/one-app/src/common/components/Article/RecommendArticleCard.tsx rename to services/one-app/src/component/Article/RecommendArticleCard.tsx index 5adad26d7..163d76430 100644 --- a/services/one-app/src/common/components/Article/RecommendArticleCard.tsx +++ b/services/one-app/src/component/Article/RecommendArticleCard.tsx @@ -1,11 +1,10 @@ -'use client'; +import { formatDateTime } from '@ahhachul/utils'; -import { DotIcon } from '@/common/assets/icons'; -import { formatDate } from '@/common/utils'; -import type { RecommendPost } from '@/model'; +import { DotIcon } from '@/asset/icon'; +import type { IRecommendPost } from '@/types'; interface Props { - post: RecommendPost; + post: IRecommendPost; } export const RecommendArticleCard = ({ post }: Props) => { @@ -19,13 +18,12 @@ export const RecommendArticleCard = ({ post }: Props) => {
    LOST112 - {formatDate(post.createdAt, false)} + {formatDateTime(post.createdAt)}
    {post?.imageUrl && (
    - {/* TODO */} {/* ; diff --git a/services/one-app/src/common/components/Comment/BaseCommentList.suspense.tsx b/services/one-app/src/component/Comment/BaseCommentList.suspense.tsx similarity index 93% rename from services/one-app/src/common/components/Comment/BaseCommentList.suspense.tsx rename to services/one-app/src/component/Comment/BaseCommentList.suspense.tsx index 71e4ac700..7d4486dbc 100644 --- a/services/one-app/src/common/components/Comment/BaseCommentList.suspense.tsx +++ b/services/one-app/src/component/Comment/BaseCommentList.suspense.tsx @@ -1,7 +1,7 @@ 'use client'; -import { useIsDeferred } from '@/common/hooks'; -import { cn } from '@/common/utils'; +import { useIsDeferred } from '@/hook'; +import { cn } from '@/util'; import { BaseSkeleton } from '../BaseSkeleton'; diff --git a/services/one-app/src/common/components/Comment/BaseCommentList.tsx b/services/one-app/src/component/Comment/BaseCommentList.tsx similarity index 94% rename from services/one-app/src/common/components/Comment/BaseCommentList.tsx rename to services/one-app/src/component/Comment/BaseCommentList.tsx index 43e3b5269..2bd414f1e 100644 --- a/services/one-app/src/common/components/Comment/BaseCommentList.tsx +++ b/services/one-app/src/component/Comment/BaseCommentList.tsx @@ -2,7 +2,7 @@ import React from 'react'; -import type { CommentList } from '@/model'; +import type { CommentList } from '@/types'; import { CommentCard } from './CommentCard'; import { EmptyCommentList } from './EmptyCommentList'; diff --git a/services/one-app/src/common/components/Comment/CommentCard.tsx b/services/one-app/src/component/Comment/CommentCard.tsx similarity index 71% rename from services/one-app/src/common/components/Comment/CommentCard.tsx rename to services/one-app/src/component/Comment/CommentCard.tsx index bf4ce6ac6..5d8050c62 100644 --- a/services/one-app/src/common/components/Comment/CommentCard.tsx +++ b/services/one-app/src/component/Comment/CommentCard.tsx @@ -1,9 +1,12 @@ 'use client'; -import { LexicalSyntaxContentParser } from '@/app/(site)/_component/Editor'; -import { EllipsisIcon } from '@/common/assets/icons'; -import { cn, formatDate } from '@/common/utils'; -import type { Comment } from '@/model'; +import { formatDateTime } from '@ahhachul/utils'; + +import { EllipsisIcon } from '@/asset/icon'; +import type { Comment } from '@/types'; +import { cn } from '@/util'; + +import { ReadonlyEditor } from '../Editor'; interface CommentCardProps { comment: Comment; @@ -24,14 +27,16 @@ export const CommentCard = ({ comment, asChild = false }: CommentCardProps) => {
    {comment.status === 'CREATED' ? ( - div>div]:p-0', '[&>div>div]:border-none')} /> ) : (
    ์‚ญ์ œ๋œ ๋Œ“๊ธ€์ž…๋‹ˆ๋‹ค.
    )} - {formatDate(comment.createdAt)} + + {formatDateTime(comment.createdAt, { format: 'short' })} +
    diff --git a/services/one-app/src/common/components/Comment/CommentTextField.tsx b/services/one-app/src/component/Comment/CommentTextField.tsx similarity index 95% rename from services/one-app/src/common/components/Comment/CommentTextField.tsx rename to services/one-app/src/component/Comment/CommentTextField.tsx index 3d843eb6a..b897e039e 100644 --- a/services/one-app/src/common/components/Comment/CommentTextField.tsx +++ b/services/one-app/src/component/Comment/CommentTextField.tsx @@ -4,8 +4,9 @@ import React from 'react'; import { EditorState } from 'lexical'; -import { Editor } from '@/app/(site)/_component/Editor'; -import { cn } from '@/common/utils'; +import { cn } from '@/util'; + +import { Editor } from '../Editor'; interface CommentTextFieldProps { placeholder: string; diff --git a/services/one-app/src/common/components/Comment/EmptyCommentList.tsx b/services/one-app/src/component/Comment/EmptyCommentList.tsx similarity index 92% rename from services/one-app/src/common/components/Comment/EmptyCommentList.tsx rename to services/one-app/src/component/Comment/EmptyCommentList.tsx index 6059f0945..6d59cfe40 100644 --- a/services/one-app/src/common/components/Comment/EmptyCommentList.tsx +++ b/services/one-app/src/component/Comment/EmptyCommentList.tsx @@ -1,6 +1,6 @@ 'use client'; -import { cn } from '@/common/utils/cn'; +import { cn } from '@/util/cn'; interface Props { className?: string; diff --git a/services/one-app/src/common/components/Comment/index.ts b/services/one-app/src/component/Comment/index.ts similarity index 100% rename from services/one-app/src/common/components/Comment/index.ts rename to services/one-app/src/component/Comment/index.ts diff --git a/services/one-app/src/common/components/ConditionalRender.spec.tsx b/services/one-app/src/component/ConditionalRender.spec.tsx similarity index 100% rename from services/one-app/src/common/components/ConditionalRender.spec.tsx rename to services/one-app/src/component/ConditionalRender.spec.tsx diff --git a/services/one-app/src/common/components/ConditionalRender.tsx b/services/one-app/src/component/ConditionalRender.tsx similarity index 100% rename from services/one-app/src/common/components/ConditionalRender.tsx rename to services/one-app/src/component/ConditionalRender.tsx diff --git a/services/one-app/src/common/components/Drawer.tsx b/services/one-app/src/component/Drawer.tsx similarity index 98% rename from services/one-app/src/common/components/Drawer.tsx rename to services/one-app/src/component/Drawer.tsx index 2af68e890..443d7ce3f 100644 --- a/services/one-app/src/common/components/Drawer.tsx +++ b/services/one-app/src/component/Drawer.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import { Drawer as DrawerPrimitive } from 'vaul'; -import { cn } from '@/common/utils/cn'; +import { cn } from '@/util/cn'; const Drawer = ({ shouldScaleBackground = true, diff --git a/services/one-app/src/app/(site)/_component/Editor/Editor.tsx b/services/one-app/src/component/Editor/Editor.tsx similarity index 84% rename from services/one-app/src/app/(site)/_component/Editor/Editor.tsx rename to services/one-app/src/component/Editor/Editor.tsx index f414d7359..6e62c890c 100644 --- a/services/one-app/src/app/(site)/_component/Editor/Editor.tsx +++ b/services/one-app/src/component/Editor/Editor.tsx @@ -7,11 +7,7 @@ import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin'; import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'; import type { EditorState } from 'lexical'; -import { - OnChangePlugin, - SpeechToTextPlugin, - SpeechToTextToolbarPlugin, -} from '@/app/(site)/_component/Editor'; +import { OnChangePlugin, SpeechToTextPlugin, SpeechToTextToolbarPlugin } from './plugin'; const theme = {}; @@ -35,12 +31,18 @@ const Placeholder = ({ type Props = { showMic?: boolean; readonly?: boolean; - placeholder?: string; + placeholder?: string | false; initialState?: string; onChange?: (editorState: EditorState | null) => void; }; -export const Editor = ({ showMic, readonly, placeholder, initialState, onChange }: Props) => { +export const Editor = ({ + showMic, + readonly, + placeholder = false, + initialState, + onChange, +}: Props) => { const initialConfig = { namespace: 'MyEditor', theme, @@ -54,7 +56,7 @@ export const Editor = ({ showMic, readonly, placeholder, initialState, onChange contentEditable={ } - placeholder={} + placeholder={placeholder && } ErrorBoundary={LexicalErrorBoundary} /> diff --git a/services/one-app/src/app/(site)/_component/Editor/LexicalSyntaxContentParser.tsx b/services/one-app/src/component/Editor/ReadonlyEditor.tsx similarity index 66% rename from services/one-app/src/app/(site)/_component/Editor/LexicalSyntaxContentParser.tsx rename to services/one-app/src/component/Editor/ReadonlyEditor.tsx index a2fb183dc..c4da73a22 100644 --- a/services/one-app/src/app/(site)/_component/Editor/LexicalSyntaxContentParser.tsx +++ b/services/one-app/src/component/Editor/ReadonlyEditor.tsx @@ -1,13 +1,13 @@ import { Editor } from '.'; -import { cn } from '@/common/utils/cn'; +import { cn } from '@/util/cn'; export type Props = { content: string; className?: string; }; -export const LexicalSyntaxContentParser = ({ content, className }: Props) => { +export const ReadonlyEditor = ({ content, className }: Props) => { return (
    diff --git a/services/one-app/src/app/(site)/_hook/useReport.ts b/services/one-app/src/component/Editor/hook/useReport.ts similarity index 100% rename from services/one-app/src/app/(site)/_hook/useReport.ts rename to services/one-app/src/component/Editor/hook/useReport.ts diff --git a/services/one-app/src/component/Editor/index.ts b/services/one-app/src/component/Editor/index.ts new file mode 100644 index 000000000..99fb4b707 --- /dev/null +++ b/services/one-app/src/component/Editor/index.ts @@ -0,0 +1,4 @@ +export * from './plugin'; + +export * from './Editor'; +export * from './ReadonlyEditor'; diff --git a/services/one-app/src/app/(site)/_component/Editor/plugins/OnChangePlugin.tsx b/services/one-app/src/component/Editor/plugin/OnChangePlugin.tsx similarity index 100% rename from services/one-app/src/app/(site)/_component/Editor/plugins/OnChangePlugin.tsx rename to services/one-app/src/component/Editor/plugin/OnChangePlugin.tsx diff --git a/services/one-app/src/app/(site)/_component/Editor/plugins/SpeechToTextPlugin.tsx b/services/one-app/src/component/Editor/plugin/SpeechToTextPlugin.tsx similarity index 98% rename from services/one-app/src/app/(site)/_component/Editor/plugins/SpeechToTextPlugin.tsx rename to services/one-app/src/component/Editor/plugin/SpeechToTextPlugin.tsx index 5e87e5666..acbdf4c37 100644 --- a/services/one-app/src/app/(site)/_component/Editor/plugins/SpeechToTextPlugin.tsx +++ b/services/one-app/src/component/Editor/plugin/SpeechToTextPlugin.tsx @@ -20,7 +20,7 @@ import { UNDO_COMMAND, } from 'lexical'; -import { useReport } from '@/app/(site)/_hook'; +import { useReport } from '../hook/useReport'; export const SPEECH_TO_TEXT_COMMAND: LexicalCommand = createCommand('SPEECH_TO_TEXT_COMMAND'); diff --git a/services/one-app/src/app/(site)/_component/Editor/plugins/SpeechToTextToolbarPlugin.tsx b/services/one-app/src/component/Editor/plugin/SpeechToTextToolbarPlugin.tsx similarity index 96% rename from services/one-app/src/app/(site)/_component/Editor/plugins/SpeechToTextToolbarPlugin.tsx rename to services/one-app/src/component/Editor/plugin/SpeechToTextToolbarPlugin.tsx index 8121c4554..abb632426 100644 --- a/services/one-app/src/app/(site)/_component/Editor/plugins/SpeechToTextToolbarPlugin.tsx +++ b/services/one-app/src/component/Editor/plugin/SpeechToTextToolbarPlugin.tsx @@ -4,7 +4,7 @@ import { useEffect, useRef, useState } from 'react'; import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; -import { MicIcon } from '@/common/assets/icons'; +import { MicIcon } from '@/asset/icon'; import { SPEECH_TO_TEXT_COMMAND } from './SpeechToTextPlugin'; diff --git a/services/one-app/src/app/(site)/_component/Editor/plugins/index.ts b/services/one-app/src/component/Editor/plugin/index.ts similarity index 100% rename from services/one-app/src/app/(site)/_component/Editor/plugins/index.ts rename to services/one-app/src/component/Editor/plugin/index.ts diff --git a/services/one-app/src/common/components/Filter/DropdownFilter.tsx b/services/one-app/src/component/Filter/DropdownFilter.tsx similarity index 56% rename from services/one-app/src/common/components/Filter/DropdownFilter.tsx rename to services/one-app/src/component/Filter/DropdownFilter.tsx index 5fac887a3..349cf15fc 100644 --- a/services/one-app/src/common/components/Filter/DropdownFilter.tsx +++ b/services/one-app/src/component/Filter/DropdownFilter.tsx @@ -3,36 +3,55 @@ import React from 'react'; import * as DropdownMenu from '@radix-ui/react-dropdown-menu'; +import { usePathname, useSearchParams } from 'next/navigation'; +import { useRouter } from 'nextjs-toploader/app'; -import { CheckIcon, ChevronDownIcon } from '@/common/assets/icons'; -import { cn, objectEntries } from '@/common/utils'; -import type { KeyOf } from '@/model'; +import { objectEntries } from '@ahhachul/utils'; -export interface DropdownFilterProps, K extends KeyOf> { - name: K; - filters: T; - options: Record; - onSelect: (key: K, value: T[K]) => void; +import { CheckIcon, ChevronDownIcon } from '@/asset/icon'; +import type { KeyOf, ObjectQueryParams } from '@/types'; +import { cn } from '@/util'; + +export interface DropdownFilterProps> { + name: string; + value: K; + options: T; } -export const DropdownFilter = , K extends KeyOf>({ - filters, - options, - onSelect, +export const DropdownFilter = >({ name, + value, + options, }: DropdownFilterProps): React.ReactElement => { - const activeValue = filters[name]; - const defaultValue = Object.keys(options)[0] as T[K]; - const isActive = activeValue !== defaultValue; + const defaultValue = Object.keys(options)[0] as KeyOf; + const isActive = defaultValue !== value; + + const router = useRouter(); + const pathname = usePathname(); + const searchParams = useSearchParams(); + + const onSelect = (newVal: string) => { + let newSearchParams = new URLSearchParams(searchParams); + + if (newVal === defaultValue) { + newSearchParams.delete(name); + } else { + newSearchParams.set(name, newVal); + } + + router.push(`${pathname}?${newSearchParams.toString()}`); + }; return ( @@ -48,8 +67,8 @@ export const DropdownFilter = , K extends KeyOf )} > onSelect(name, newValue as T[K])} + value={value as string} + onValueChange={newValue => onSelect(newValue)} > {objectEntries(options).map(([val, label]) => ( void; + options: ObjectQueryParams; } -export const ResetFilter = ({ activatedCount, handleReset }: Props) => { - const renderThis = activatedCount > 0; +export const ResetFilter = ({ options }: Props) => { + const router = useRouter(); + const pathname = usePathname(); + + const clearSearchParams = () => { + router.push(pathname); + }; + + const searchParams = useSearchParams(); + const renderThis = !!searchParams.toString(); + const activatedCount = Object.keys(options).filter( + key => searchParams.has(key) && searchParams.get(key) !== options[key], + ).length; return (