forked from baron/baron-sso
ci: add code check badges and coverage reports
This commit is contained in:
@@ -4,6 +4,8 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- dev
|
- dev
|
||||||
|
paths-ignore:
|
||||||
|
- "docs/badges/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- dev
|
- dev
|
||||||
@@ -49,6 +51,14 @@ on:
|
|||||||
required: true
|
required: true
|
||||||
type: boolean
|
type: boolean
|
||||||
default: true
|
default: true
|
||||||
|
run_front_coverage:
|
||||||
|
description: "Run adminfront/devfront/orgfront Vitest coverage and upload reports"
|
||||||
|
required: true
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
@@ -148,6 +158,51 @@ jobs:
|
|||||||
cd userfront
|
cd userfront
|
||||||
flutter analyze --no-fatal-warnings --no-fatal-infos
|
flutter analyze --no-fatal-warnings --no-fatal-infos
|
||||||
|
|
||||||
|
biome-check:
|
||||||
|
if: ${{ github.event_name != 'workflow_dispatch' || inputs.run_lint == true }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "24"
|
||||||
|
|
||||||
|
- name: Install adminfront dependencies
|
||||||
|
run: |
|
||||||
|
cd adminfront
|
||||||
|
npx pnpm install -C ../common --no-frozen-lockfile
|
||||||
|
|
||||||
|
- name: Biome check adminfront
|
||||||
|
run: |
|
||||||
|
cd adminfront
|
||||||
|
npx biome check . --formatter-enabled=false --organize-imports-enabled=false
|
||||||
|
npx biome check . --linter-enabled=false --organize-imports-enabled=false
|
||||||
|
|
||||||
|
- name: Install devfront dependencies
|
||||||
|
run: |
|
||||||
|
cd devfront
|
||||||
|
npx pnpm install -C ../common --no-frozen-lockfile
|
||||||
|
|
||||||
|
- name: Biome check devfront
|
||||||
|
run: |
|
||||||
|
cd devfront
|
||||||
|
npx biome check . --formatter-enabled=false --organize-imports-enabled=false
|
||||||
|
npx biome check . --linter-enabled=false --organize-imports-enabled=false
|
||||||
|
|
||||||
|
- name: Install orgfront dependencies
|
||||||
|
run: |
|
||||||
|
cd orgfront
|
||||||
|
npx pnpm install -C ../common --no-frozen-lockfile
|
||||||
|
|
||||||
|
- name: Biome check orgfront
|
||||||
|
run: |
|
||||||
|
cd orgfront
|
||||||
|
npx biome check . --formatter-enabled=false --organize-imports-enabled=false
|
||||||
|
npx biome check . --linter-enabled=false --organize-imports-enabled=false
|
||||||
|
|
||||||
backend-tests:
|
backend-tests:
|
||||||
needs: lint
|
needs: lint
|
||||||
if: ${{ always() && (github.event_name != 'workflow_dispatch' || inputs.run_backend_tests == true) }}
|
if: ${{ always() && (github.event_name != 'workflow_dispatch' || inputs.run_backend_tests == true) }}
|
||||||
@@ -570,6 +625,159 @@ jobs:
|
|||||||
userfront-e2e/test-results
|
userfront-e2e/test-results
|
||||||
if-no-files-found: ignore
|
if-no-files-found: ignore
|
||||||
|
|
||||||
|
front-vitest-coverage:
|
||||||
|
needs: lint
|
||||||
|
if: ${{ always() && (github.event_name != 'workflow_dispatch' || inputs.run_front_coverage == true) }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "24"
|
||||||
|
|
||||||
|
- name: Install front workspace dependencies
|
||||||
|
run: |
|
||||||
|
mkdir -p reports
|
||||||
|
set +e
|
||||||
|
npm install -g pnpm
|
||||||
|
cd common
|
||||||
|
pnpm install --no-frozen-lockfile --shamefully-hoist 2>&1 | tee ../reports/front-coverage-install.log
|
||||||
|
install_exit_code=${PIPESTATUS[0]}
|
||||||
|
cd ..
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ "$install_exit_code" -ne 0 ]; then
|
||||||
|
{
|
||||||
|
echo "# Front Vitest Coverage Failure Report"
|
||||||
|
echo
|
||||||
|
echo "- Workflow: \`${GITHUB_WORKFLOW:-Code Check}\`"
|
||||||
|
echo "- Job: \`front-vitest-coverage\`"
|
||||||
|
echo "- Reason: \`Dependency install failed\`"
|
||||||
|
echo "- Exit Code: \`$install_exit_code\`"
|
||||||
|
echo
|
||||||
|
echo "## Command"
|
||||||
|
echo "\`cd common && pnpm install --no-frozen-lockfile --shamefully-hoist\`"
|
||||||
|
echo
|
||||||
|
echo "## Install Log Tail (last 200 lines)"
|
||||||
|
echo '```text'
|
||||||
|
tail -n 200 reports/front-coverage-install.log
|
||||||
|
echo '```'
|
||||||
|
} > reports/front-vitest-coverage-failure-report.md
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Run adminfront Vitest coverage
|
||||||
|
run: |
|
||||||
|
set +e
|
||||||
|
cd adminfront
|
||||||
|
pnpm run test:coverage 2>&1 | tee ../reports/adminfront-vitest-coverage.log
|
||||||
|
test_exit_code=${PIPESTATUS[0]}
|
||||||
|
cd ..
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ "$test_exit_code" -ne 0 ]; then
|
||||||
|
{
|
||||||
|
echo "# Front Vitest Coverage Failure Report"
|
||||||
|
echo
|
||||||
|
echo "- Workflow: \`${GITHUB_WORKFLOW:-Code Check}\`"
|
||||||
|
echo "- Job: \`front-vitest-coverage\`"
|
||||||
|
echo "- Package: \`adminfront\`"
|
||||||
|
echo "- Exit Code: \`$test_exit_code\`"
|
||||||
|
echo
|
||||||
|
echo "## Log Tail (last 200 lines)"
|
||||||
|
echo '```text'
|
||||||
|
tail -n 200 reports/adminfront-vitest-coverage.log
|
||||||
|
echo '```'
|
||||||
|
} > reports/front-vitest-coverage-failure-report.md
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Run devfront Vitest coverage
|
||||||
|
run: |
|
||||||
|
set +e
|
||||||
|
cd devfront
|
||||||
|
pnpm run test:coverage 2>&1 | tee ../reports/devfront-vitest-coverage.log
|
||||||
|
test_exit_code=${PIPESTATUS[0]}
|
||||||
|
cd ..
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ "$test_exit_code" -ne 0 ]; then
|
||||||
|
{
|
||||||
|
echo "# Front Vitest Coverage Failure Report"
|
||||||
|
echo
|
||||||
|
echo "- Workflow: \`${GITHUB_WORKFLOW:-Code Check}\`"
|
||||||
|
echo "- Job: \`front-vitest-coverage\`"
|
||||||
|
echo "- Package: \`devfront\`"
|
||||||
|
echo "- Exit Code: \`$test_exit_code\`"
|
||||||
|
echo
|
||||||
|
echo "## Log Tail (last 200 lines)"
|
||||||
|
echo '```text'
|
||||||
|
tail -n 200 reports/devfront-vitest-coverage.log
|
||||||
|
echo '```'
|
||||||
|
} > reports/front-vitest-coverage-failure-report.md
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Run orgfront Vitest coverage
|
||||||
|
run: |
|
||||||
|
set +e
|
||||||
|
cd orgfront
|
||||||
|
pnpm run test:coverage 2>&1 | tee ../reports/orgfront-vitest-coverage.log
|
||||||
|
test_exit_code=${PIPESTATUS[0]}
|
||||||
|
cd ..
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ "$test_exit_code" -ne 0 ]; then
|
||||||
|
{
|
||||||
|
echo "# Front Vitest Coverage Failure Report"
|
||||||
|
echo
|
||||||
|
echo "- Workflow: \`${GITHUB_WORKFLOW:-Code Check}\`"
|
||||||
|
echo "- Job: \`front-vitest-coverage\`"
|
||||||
|
echo "- Package: \`orgfront\`"
|
||||||
|
echo "- Exit Code: \`$test_exit_code\`"
|
||||||
|
echo
|
||||||
|
echo "## Log Tail (last 200 lines)"
|
||||||
|
echo '```text'
|
||||||
|
tail -n 200 reports/orgfront-vitest-coverage.log
|
||||||
|
echo '```'
|
||||||
|
} > reports/front-vitest-coverage-failure-report.md
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Generate Vitest coverage summary
|
||||||
|
run: |
|
||||||
|
node scripts/summarize_vitest_coverage.mjs
|
||||||
|
cat reports/vitest-coverage-summary.md >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
|
||||||
|
- name: Publish front Vitest coverage failure summary
|
||||||
|
if: ${{ failure() }}
|
||||||
|
run: |
|
||||||
|
if [ -f reports/front-vitest-coverage-failure-report.md ]; then
|
||||||
|
cat reports/front-vitest-coverage-failure-report.md >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Upload front Vitest coverage report artifact
|
||||||
|
if: ${{ always() }}
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
continue-on-error: true
|
||||||
|
with:
|
||||||
|
name: front-vitest-coverage-report
|
||||||
|
path: |
|
||||||
|
reports/vitest-coverage-summary.md
|
||||||
|
reports/vitest-coverage-summary.json
|
||||||
|
reports/front-vitest-coverage-failure-report.md
|
||||||
|
reports/front-coverage-install.log
|
||||||
|
reports/adminfront-vitest-coverage.log
|
||||||
|
reports/devfront-vitest-coverage.log
|
||||||
|
reports/orgfront-vitest-coverage.log
|
||||||
|
adminfront/coverage
|
||||||
|
devfront/coverage
|
||||||
|
orgfront/coverage
|
||||||
|
if-no-files-found: ignore
|
||||||
|
|
||||||
adminfront-tests:
|
adminfront-tests:
|
||||||
needs: lint
|
needs: lint
|
||||||
if: ${{ always() && (github.event_name != 'workflow_dispatch' || inputs.run_adminfront_tests == true) }}
|
if: ${{ always() && (github.event_name != 'workflow_dispatch' || inputs.run_adminfront_tests == true) }}
|
||||||
@@ -1021,3 +1229,63 @@ jobs:
|
|||||||
orgfront/playwright-report
|
orgfront/playwright-report
|
||||||
orgfront/test-results
|
orgfront/test-results
|
||||||
if-no-files-found: ignore
|
if-no-files-found: ignore
|
||||||
|
|
||||||
|
badge-updater:
|
||||||
|
needs:
|
||||||
|
- lint
|
||||||
|
- biome-check
|
||||||
|
- backend-tests
|
||||||
|
- userfront-tests
|
||||||
|
- userfront-e2e-tests
|
||||||
|
- front-vitest-coverage
|
||||||
|
- adminfront-tests
|
||||||
|
- devfront-tests
|
||||||
|
- orgfront-tests
|
||||||
|
if: ${{ always() && github.event_name != 'pull_request' && github.ref == 'refs/heads/dev' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "24"
|
||||||
|
|
||||||
|
- name: Download Vitest coverage report artifact
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
continue-on-error: true
|
||||||
|
with:
|
||||||
|
name: front-vitest-coverage-report
|
||||||
|
path: badge-artifacts/front-vitest-coverage-report
|
||||||
|
|
||||||
|
- name: Update badge files
|
||||||
|
env:
|
||||||
|
LINT_RESULT: ${{ needs.lint.result }}
|
||||||
|
BIOME_RESULT: ${{ needs['biome-check'].result }}
|
||||||
|
BACKEND_RESULT: ${{ needs['backend-tests'].result }}
|
||||||
|
USERFRONT_RESULT: ${{ needs['userfront-tests'].result }}
|
||||||
|
USERFRONT_E2E_RESULT: ${{ needs['userfront-e2e-tests'].result }}
|
||||||
|
USERFRONT_E2E_FULL: ${{ github.event_name == 'workflow_dispatch' && inputs.run_userfront_e2e_full == true }}
|
||||||
|
COVERAGE_RESULT: ${{ needs['front-vitest-coverage'].result }}
|
||||||
|
ADMINFRONT_RESULT: ${{ needs['adminfront-tests'].result }}
|
||||||
|
DEVFRONT_RESULT: ${{ needs['devfront-tests'].result }}
|
||||||
|
ORGFRONT_RESULT: ${{ needs['orgfront-tests'].result }}
|
||||||
|
run: |
|
||||||
|
node scripts/update_code_check_badges.mjs
|
||||||
|
cat docs/badges/badges.json
|
||||||
|
|
||||||
|
- name: Commit badge updates
|
||||||
|
run: |
|
||||||
|
if [ -z "$(git status --porcelain docs/badges)" ]; then
|
||||||
|
echo "No badge changes."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
git config user.name "gitea-actions"
|
||||||
|
git config user.email "gitea-actions@hmac.kr"
|
||||||
|
git add docs/badges
|
||||||
|
git commit -m "chore: update code check badges [skip ci]"
|
||||||
|
git push
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -50,6 +50,9 @@ orgfront/test-results/
|
|||||||
adminfront/playwright-report/
|
adminfront/playwright-report/
|
||||||
devfront/playwright-report/
|
devfront/playwright-report/
|
||||||
orgfront/playwright-report/
|
orgfront/playwright-report/
|
||||||
|
adminfront/coverage/
|
||||||
|
devfront/coverage/
|
||||||
|
orgfront/coverage/
|
||||||
orgfront/node_modules/
|
orgfront/node_modules/
|
||||||
orgfront/dist/
|
orgfront/dist/
|
||||||
orgfront/.vite/
|
orgfront/.vite/
|
||||||
|
|||||||
10
README.md
10
README.md
@@ -1,5 +1,15 @@
|
|||||||
# Baron SSO
|
# Baron SSO
|
||||||
|
|
||||||
|
[](https://gitea.hmac.kr/baron/baron-sso/actions/workflows/code_check.yml?branch=dev)
|
||||||
|
[](https://gitea.hmac.kr/baron/baron-sso/actions/workflows/code_check.yml?branch=dev)
|
||||||
|
[](https://gitea.hmac.kr/baron/baron-sso/actions/workflows/code_check.yml?branch=dev)
|
||||||
|
[](https://gitea.hmac.kr/baron/baron-sso/actions/workflows/code_check.yml?branch=dev)
|
||||||
|
[](https://gitea.hmac.kr/baron/baron-sso/actions/workflows/code_check.yml?branch=dev)
|
||||||
|
[](https://gitea.hmac.kr/baron/baron-sso/actions/workflows/code_check.yml?branch=dev)
|
||||||
|
[](https://gitea.hmac.kr/baron/baron-sso/actions/workflows/code_check.yml?branch=dev)
|
||||||
|
|
||||||
|
badge는 `Code Check`가 dev 브랜치에서 갱신합니다. 최신 HTML/LCOV/JSON summary는 Gitea `Code Check`의 `front-vitest-coverage-report` artifact에서 확인할 수 있습니다.
|
||||||
|
|
||||||
**Baron 로그인**은 화이트 라벨링된 가족사의 모든 소프트웨어 Auth를 총괄하는 사용자 인증/인가 허브입니다.
|
**Baron 로그인**은 화이트 라벨링된 가족사의 모든 소프트웨어 Auth를 총괄하는 사용자 인증/인가 허브입니다.
|
||||||
|
|
||||||
## 📂 프로젝트 구조 (Project Structure)
|
## 📂 프로젝트 구조 (Project Structure)
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
{
|
{
|
||||||
"extends": ["../common/config/biome.base.json"],
|
"root": true,
|
||||||
"files": {
|
"extends": ["../common/config/biome.base.json"]
|
||||||
"ignore": [".vite"]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
577
adminfront/package-lock.json
generated
577
adminfront/package-lock.json
generated
@@ -32,6 +32,7 @@
|
|||||||
"zod": "^4.4.3"
|
"zod": "^4.4.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@biomejs/biome": "2.4.16",
|
||||||
"@playwright/test": "^1.60.0",
|
"@playwright/test": "^1.60.0",
|
||||||
"@testing-library/jest-dom": "^6.9.1",
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
"@testing-library/react": "^16.3.2",
|
"@testing-library/react": "^16.3.2",
|
||||||
@@ -41,13 +42,15 @@
|
|||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@types/react-router-dom": "^5.3.3",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
"@vitejs/plugin-react": "^6.0.1",
|
"@vitejs/plugin-react": "^6.0.1",
|
||||||
|
"@vitest/coverage-v8": "4.1.6",
|
||||||
"autoprefixer": "^10.5.0",
|
"autoprefixer": "^10.5.0",
|
||||||
"jsdom": "^28.1.0",
|
"jsdom": "^28.1.0",
|
||||||
|
"playwright": "1.60.0",
|
||||||
"postcss": "^8.5.14",
|
"postcss": "^8.5.14",
|
||||||
"tailwindcss": "^3.4.19",
|
"tailwindcss": "^3.4.19",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"typescript": "^6.0.3",
|
"typescript": "^6.0.3",
|
||||||
"vite": "^8.0.12",
|
"vite": "^8.0.14",
|
||||||
"vitest": "^4.1.6"
|
"vitest": "^4.1.6"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -130,14 +133,14 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
"version": "7.29.0",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
|
||||||
"integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
|
"integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-validator-identifier": "^7.28.5",
|
"@babel/helper-validator-identifier": "^7.29.7",
|
||||||
"js-tokens": "^4.0.0",
|
"js-tokens": "^4.0.0",
|
||||||
"picocolors": "^1.1.1"
|
"picocolors": "^1.1.1"
|
||||||
},
|
},
|
||||||
@@ -145,17 +148,42 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helper-validator-identifier": {
|
"node_modules/@babel/helper-string-parser": {
|
||||||
"version": "7.28.5",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
|
||||||
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
|
"integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/helper-validator-identifier": {
|
||||||
|
"version": "7.29.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
|
||||||
|
"integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@babel/parser": {
|
||||||
|
"version": "7.29.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
|
||||||
|
"integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/types": "^7.29.7"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"parser": "bin/babel-parser.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/runtime": {
|
"node_modules/@babel/runtime": {
|
||||||
"version": "7.29.2",
|
"version": "7.29.2",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
|
||||||
@@ -166,6 +194,205 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/types": {
|
||||||
|
"version": "7.29.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
|
||||||
|
"integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/helper-string-parser": "^7.29.7",
|
||||||
|
"@babel/helper-validator-identifier": "^7.29.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@bcoe/v8-coverage": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@biomejs/biome": {
|
||||||
|
"version": "2.4.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.4.16.tgz",
|
||||||
|
"integrity": "sha512-x9ajFh1zChVybCiM3TN6OD4phAqLgtPZjFrZF+aTMYCPjwBO+k529TX7PPsAqtGNLeV4UgzwQnowEgS7bGmzcA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT OR Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"biome": "bin/biome"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.21.3"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/biome"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@biomejs/cli-darwin-arm64": "2.4.16",
|
||||||
|
"@biomejs/cli-darwin-x64": "2.4.16",
|
||||||
|
"@biomejs/cli-linux-arm64": "2.4.16",
|
||||||
|
"@biomejs/cli-linux-arm64-musl": "2.4.16",
|
||||||
|
"@biomejs/cli-linux-x64": "2.4.16",
|
||||||
|
"@biomejs/cli-linux-x64-musl": "2.4.16",
|
||||||
|
"@biomejs/cli-win32-arm64": "2.4.16",
|
||||||
|
"@biomejs/cli-win32-x64": "2.4.16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@biomejs/cli-darwin-arm64": {
|
||||||
|
"version": "2.4.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.4.16.tgz",
|
||||||
|
"integrity": "sha512-wxPvu4XOA85YJk9ixSWUmq/QBHbid85BISbOAqqBM/5xQpPk9ayjk5375tOlSC0BeCwNSbPFafQBm+vBumXq0A==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT OR Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.21.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@biomejs/cli-darwin-x64": {
|
||||||
|
"version": "2.4.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.4.16.tgz",
|
||||||
|
"integrity": "sha512-xFCqGPwYusQJp4N4NJLi1XJiZqjwFdjhT+KqtNy+Ug3qgfczqnTa6MSDvxJF6TkuDLoYJItMapz6tAf7kCekFw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT OR Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.21.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@biomejs/cli-linux-arm64": {
|
||||||
|
"version": "2.4.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.4.16.tgz",
|
||||||
|
"integrity": "sha512-2kFb4//jxfZaP6D+Rj5VkHkxgyD9EoRAVBEQb8PKRv+s4NO2zYNJKXFaJmK1CmhufJOWEfpHKaRbOja7qjmdhQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "MIT OR Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.21.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@biomejs/cli-linux-arm64-musl": {
|
||||||
|
"version": "2.4.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.16.tgz",
|
||||||
|
"integrity": "sha512-oYxnW0ARfJkr72ezzF2OR8N/rtkgLUQeYtF8cFhVswbknHxtTcmzSsanVJP8yQKnGpGpc2ck6c5zLvHahL6Cbg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
|
"license": "MIT OR Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.21.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@biomejs/cli-linux-x64": {
|
||||||
|
"version": "2.4.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.4.16.tgz",
|
||||||
|
"integrity": "sha512-NbcBbi/nJqn5baae6wqRXdS7Gadf2uRpehSh6vMSYpG8OhkXl/Xg8aorWrJ+9VWqAT5ml90alLvorkpMW0nBwQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "MIT OR Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.21.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@biomejs/cli-linux-x64-musl": {
|
||||||
|
"version": "2.4.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.16.tgz",
|
||||||
|
"integrity": "sha512-iHDS+MCM65DPqWGu+ECC3uoALyj2H7F4nVUPxIPjz/PIl94EUu+EDfGZDzFP+NY1EOPVt9NQvwFqq7HdMmowdg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
|
"license": "MIT OR Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.21.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@biomejs/cli-win32-arm64": {
|
||||||
|
"version": "2.4.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.4.16.tgz",
|
||||||
|
"integrity": "sha512-0rgImMsNb5v/chhkIFe3wu7PEFClS6RBAYUijGL9UsYN3PanSaoK24HSSuSJb1pYbYYVjzAyZTl3gtjJ84BM8A==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT OR Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.21.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@biomejs/cli-win32-x64": {
|
||||||
|
"version": "2.4.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.4.16.tgz",
|
||||||
|
"integrity": "sha512-Kp85jgoBHa05gix6UIRjfCDiUV3w/8VIdZ247VyyO2gEjaw12WEVhdIjlxp/AMzXxqxQwbxNTDVZ3Mwd2RG5rw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT OR Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.21.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@bramus/specificity": {
|
"node_modules/@bramus/specificity": {
|
||||||
"version": "2.4.2",
|
"version": "2.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz",
|
||||||
@@ -506,9 +733,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@oxc-project/types": {
|
"node_modules/@oxc-project/types": {
|
||||||
"version": "0.130.0",
|
"version": "0.132.0",
|
||||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.130.0.tgz",
|
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.132.0.tgz",
|
||||||
"integrity": "sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==",
|
"integrity": "sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
@@ -2002,9 +2229,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@rolldown/binding-android-arm64": {
|
"node_modules/@rolldown/binding-android-arm64": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.2.tgz",
|
||||||
"integrity": "sha512-fJI3I0r3C3Oj/zdBCpaCmBRZYf07xpaq4yCfDDoSFm+beWNzbIl26puW8RraUdugoJw/95zerNOn6jasAhzSmg==",
|
"integrity": "sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -2019,9 +2246,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rolldown/binding-darwin-arm64": {
|
"node_modules/@rolldown/binding-darwin-arm64": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.2.tgz",
|
||||||
"integrity": "sha512-cKnAhWEsV7TPcA/5EAteDp6KcJZBQ2G+BqE7zayMMi7kMvwRsbv7WT9aOnn0WNl4SKEIf43vjS31iUPu80nzXg==",
|
"integrity": "sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -2036,9 +2263,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rolldown/binding-darwin-x64": {
|
"node_modules/@rolldown/binding-darwin-x64": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.2.tgz",
|
||||||
"integrity": "sha512-YKrVwQjIRBPo+5G/u03wGjbdy4q7pyzCe93DK9VJ7zkVmeg8LJ7GbgsiHWdR4xSoe4CAXRD7Bcjgbtr64bkXNg==",
|
"integrity": "sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -2053,9 +2280,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rolldown/binding-freebsd-x64": {
|
"node_modules/@rolldown/binding-freebsd-x64": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.2.tgz",
|
||||||
"integrity": "sha512-z/oBsREo46SsFqBwYtFe0kpJeBijAT48O/WXLI4suiCLBkr03RTtTJMCzSdDd2znlh8VJizL09XVkQgk8IZonw==",
|
"integrity": "sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -2070,9 +2297,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
|
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.2.tgz",
|
||||||
"integrity": "sha512-ik8q7GM11zxvYxFc2PeDcT6TBvhCQMaUxfph/M5l9sKuTs/Sjg3L+Byw0F7w0ZVLBZmx30P+gG0ECzzN+MFcmQ==",
|
"integrity": "sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -2087,13 +2314,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rolldown/binding-linux-arm64-gnu": {
|
"node_modules/@rolldown/binding-linux-arm64-gnu": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.2.tgz",
|
||||||
"integrity": "sha512-QoSx2EkyrrdZ6kcyE8stqZ62t0Yra8Fs5ia9lOxJrh6TMQJK7gQKmscdTHf7pOXKREKrVwOtJcQG3qVSfc866A==",
|
"integrity": "sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2104,13 +2334,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rolldown/binding-linux-arm64-musl": {
|
"node_modules/@rolldown/binding-linux-arm64-musl": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.2.tgz",
|
||||||
"integrity": "sha512-uwNwFpwKeNiZawfAWBgg0VIztPTV3ihhh1vV334h9ivnNLorxnQMU6Fz8wG1Zb4Qh9LC1/MkcyT3YlDXG3Rsgg==",
|
"integrity": "sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2121,13 +2354,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
|
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.2.tgz",
|
||||||
"integrity": "sha512-zY1bul7OWr7DFBiJ++wofXvnr8B45ce3QsQUhKrIhXsygAh7bTkwyeM1bi1a2g5C/yC/N8TZyGDEoMfm/l9mpg==",
|
"integrity": "sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2138,13 +2374,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rolldown/binding-linux-s390x-gnu": {
|
"node_modules/@rolldown/binding-linux-s390x-gnu": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.2.tgz",
|
||||||
"integrity": "sha512-0frlsT/f4Ft6I7SMESTKnF3cZsdicQn1dCMkF/jT9wDLE+gGoiQfv1nmT9e+s7s/fekvvy6tZM2jHvI2tkbJDQ==",
|
"integrity": "sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2155,13 +2394,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rolldown/binding-linux-x64-gnu": {
|
"node_modules/@rolldown/binding-linux-x64-gnu": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.2.tgz",
|
||||||
"integrity": "sha512-XABVmGp9Tg0WspTVvwduTc4fpqy6JnAUrSQe6OuyqD/03nI7r0O9OWUkMIwFrjKAIqolvqoA4ZrJppgwE0Gxmw==",
|
"integrity": "sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2172,13 +2414,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rolldown/binding-linux-x64-musl": {
|
"node_modules/@rolldown/binding-linux-x64-musl": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.2.tgz",
|
||||||
"integrity": "sha512-bV4fzswuzVcKD90o/VM6QqKxnxlDq0g2BISDLNVmxrnhpv1DDbyPhCIjYfvzYLV+MvkKKnQt2Q6AO86SEBULUQ==",
|
"integrity": "sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2189,9 +2434,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rolldown/binding-openharmony-arm64": {
|
"node_modules/@rolldown/binding-openharmony-arm64": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.2.tgz",
|
||||||
"integrity": "sha512-/Mh0Zhq3OP7fVs0kcQHZP6lZEthMGTaSf8UBQYSFEZDWGXXlEC+nJ6EqenaK2t4LBXMe3A+K/G2BVXXdtOr4PQ==",
|
"integrity": "sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -2206,9 +2451,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rolldown/binding-wasm32-wasi": {
|
"node_modules/@rolldown/binding-wasm32-wasi": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.2.tgz",
|
||||||
"integrity": "sha512-+1xc9X45l8ufsBAm6Gjvx2qDRIY9lTVt0cgWNcJ+1gdhXvkbxePA60yRTwSTuXL09CMhyJmjpV7E3NoyxbqFQQ==",
|
"integrity": "sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"wasm32"
|
"wasm32"
|
||||||
],
|
],
|
||||||
@@ -2225,9 +2470,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.2.tgz",
|
||||||
"integrity": "sha512-1D+UqZdfnuR+Jy1GgMJwi85bD40H21uNmOPRWQhw4oRSuolZ/B5rixZ45DK2KXOTCvmVCecauWgEhbw8bI7tOw==",
|
"integrity": "sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -2242,9 +2487,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rolldown/binding-win32-x64-msvc": {
|
"node_modules/@rolldown/binding-win32-x64-msvc": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.2.tgz",
|
||||||
"integrity": "sha512-INAycaWuhlOK3wk4mRHGsdgwYWmd9cChdPdE9bwWmy6rn9VqVNYNFGhOdXrofXUxwHIncSiPNb8tNm8knDVIeQ==",
|
"integrity": "sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -2572,6 +2817,37 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@vitest/coverage-v8": {
|
||||||
|
"version": "4.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.6.tgz",
|
||||||
|
"integrity": "sha512-36l628fQ/9a/8ihy97eOtEnvWQEdqULQOJtcaxtoNq0G1w3Mxd4szSahOaMM9/NGyZ+hyKcMtIW/WIxq0XQViQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@bcoe/v8-coverage": "^1.0.2",
|
||||||
|
"@vitest/utils": "4.1.6",
|
||||||
|
"ast-v8-to-istanbul": "^1.0.0",
|
||||||
|
"istanbul-lib-coverage": "^3.2.2",
|
||||||
|
"istanbul-lib-report": "^3.0.1",
|
||||||
|
"istanbul-reports": "^3.2.0",
|
||||||
|
"magicast": "^0.5.2",
|
||||||
|
"obug": "^2.1.1",
|
||||||
|
"std-env": "^4.0.0-rc.1",
|
||||||
|
"tinyrainbow": "^3.1.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@vitest/browser": "4.1.6",
|
||||||
|
"vitest": "4.1.6"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@vitest/browser": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@vitest/expect": {
|
"node_modules/@vitest/expect": {
|
||||||
"version": "4.1.6",
|
"version": "4.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.6.tgz",
|
||||||
@@ -2782,6 +3058,25 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ast-v8-to-istanbul": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-dKmJxJsGItLmc5CYZKuEjuG6GnBs6PG4gohMhyFOWKaNQoYCuRZJDECaBlHmcG0lv2wc2E0uU8lESmBEumC3DQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/trace-mapping": "^0.3.31",
|
||||||
|
"estree-walker": "^3.0.3",
|
||||||
|
"js-tokens": "^10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ast-v8-to-istanbul/node_modules/js-tokens": {
|
||||||
|
"version": "10.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz",
|
||||||
|
"integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/asynckit": {
|
"node_modules/asynckit": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
@@ -3541,6 +3836,16 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/has-flag": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/has-symbols": {
|
"node_modules/has-symbols": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||||
@@ -3593,6 +3898,13 @@
|
|||||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/html-escaper": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/http-proxy-agent": {
|
"node_modules/http-proxy-agent": {
|
||||||
"version": "7.0.2",
|
"version": "7.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
|
||||||
@@ -3709,6 +4021,45 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/istanbul-lib-coverage": {
|
||||||
|
"version": "3.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
|
||||||
|
"integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/istanbul-lib-report": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"istanbul-lib-coverage": "^3.0.0",
|
||||||
|
"make-dir": "^4.0.0",
|
||||||
|
"supports-color": "^7.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/istanbul-reports": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"html-escaper": "^2.0.0",
|
||||||
|
"istanbul-lib-report": "^3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/jiti": {
|
"node_modules/jiti": {
|
||||||
"version": "1.21.7",
|
"version": "1.21.7",
|
||||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
|
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
|
||||||
@@ -4122,6 +4473,34 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/magicast": {
|
||||||
|
"version": "0.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.3.tgz",
|
||||||
|
"integrity": "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/parser": "^7.29.3",
|
||||||
|
"@babel/types": "^7.29.0",
|
||||||
|
"source-map-js": "^1.2.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/make-dir": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"semver": "^7.5.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/math-intrinsics": {
|
"node_modules/math-intrinsics": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
@@ -4390,9 +4769,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.5.14",
|
"version": "8.5.15",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
|
||||||
"integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
|
"integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -4410,7 +4789,7 @@
|
|||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.11",
|
"nanoid": "^3.3.12",
|
||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
"source-map-js": "^1.2.1"
|
"source-map-js": "^1.2.1"
|
||||||
},
|
},
|
||||||
@@ -4854,13 +5233,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rolldown": {
|
"node_modules/rolldown": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.2.tgz",
|
||||||
"integrity": "sha512-X0KQHljNnEkWNqqiz9zJrGunh1B0HgOxLXvnFpCOcadzcy5qohZ3tqMEUg00vncoRovXuK3ZqCT9KnnKzoInFQ==",
|
"integrity": "sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@oxc-project/types": "=0.130.0",
|
"@oxc-project/types": "=0.132.0",
|
||||||
"@rolldown/pluginutils": "^1.0.0"
|
"@rolldown/pluginutils": "^1.0.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -4870,21 +5249,21 @@
|
|||||||
"node": "^20.19.0 || >=22.12.0"
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@rolldown/binding-android-arm64": "1.0.1",
|
"@rolldown/binding-android-arm64": "1.0.2",
|
||||||
"@rolldown/binding-darwin-arm64": "1.0.1",
|
"@rolldown/binding-darwin-arm64": "1.0.2",
|
||||||
"@rolldown/binding-darwin-x64": "1.0.1",
|
"@rolldown/binding-darwin-x64": "1.0.2",
|
||||||
"@rolldown/binding-freebsd-x64": "1.0.1",
|
"@rolldown/binding-freebsd-x64": "1.0.2",
|
||||||
"@rolldown/binding-linux-arm-gnueabihf": "1.0.1",
|
"@rolldown/binding-linux-arm-gnueabihf": "1.0.2",
|
||||||
"@rolldown/binding-linux-arm64-gnu": "1.0.1",
|
"@rolldown/binding-linux-arm64-gnu": "1.0.2",
|
||||||
"@rolldown/binding-linux-arm64-musl": "1.0.1",
|
"@rolldown/binding-linux-arm64-musl": "1.0.2",
|
||||||
"@rolldown/binding-linux-ppc64-gnu": "1.0.1",
|
"@rolldown/binding-linux-ppc64-gnu": "1.0.2",
|
||||||
"@rolldown/binding-linux-s390x-gnu": "1.0.1",
|
"@rolldown/binding-linux-s390x-gnu": "1.0.2",
|
||||||
"@rolldown/binding-linux-x64-gnu": "1.0.1",
|
"@rolldown/binding-linux-x64-gnu": "1.0.2",
|
||||||
"@rolldown/binding-linux-x64-musl": "1.0.1",
|
"@rolldown/binding-linux-x64-musl": "1.0.2",
|
||||||
"@rolldown/binding-openharmony-arm64": "1.0.1",
|
"@rolldown/binding-openharmony-arm64": "1.0.2",
|
||||||
"@rolldown/binding-wasm32-wasi": "1.0.1",
|
"@rolldown/binding-wasm32-wasi": "1.0.2",
|
||||||
"@rolldown/binding-win32-arm64-msvc": "1.0.1",
|
"@rolldown/binding-win32-arm64-msvc": "1.0.2",
|
||||||
"@rolldown/binding-win32-x64-msvc": "1.0.1"
|
"@rolldown/binding-win32-x64-msvc": "1.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/run-parallel": {
|
"node_modules/run-parallel": {
|
||||||
@@ -4930,6 +5309,19 @@
|
|||||||
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
|
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/semver": {
|
||||||
|
"version": "7.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz",
|
||||||
|
"integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/set-cookie-parser": {
|
"node_modules/set-cookie-parser": {
|
||||||
"version": "2.7.2",
|
"version": "2.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
|
||||||
@@ -5003,6 +5395,19 @@
|
|||||||
"node": ">=16 || 14 >=14.17"
|
"node": ">=16 || 14 >=14.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/supports-color": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"has-flag": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/supports-preserve-symlinks-flag": {
|
"node_modules/supports-preserve-symlinks-flag": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||||
@@ -5373,16 +5778,16 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "8.0.13",
|
"version": "8.0.14",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.13.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.14.tgz",
|
||||||
"integrity": "sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw==",
|
"integrity": "sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lightningcss": "^1.32.0",
|
"lightningcss": "^1.32.0",
|
||||||
"picomatch": "^4.0.4",
|
"picomatch": "^4.0.4",
|
||||||
"postcss": "^8.5.14",
|
"postcss": "^8.5.15",
|
||||||
"rolldown": "1.0.1",
|
"rolldown": "1.0.2",
|
||||||
"tinyglobby": "^0.2.16"
|
"tinyglobby": "^0.2.16"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"format": "biome format . --write",
|
"format": "biome format . --write",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"test": "playwright test",
|
"test": "playwright test",
|
||||||
|
"test:coverage": "vitest run --coverage",
|
||||||
"test:unit": "vitest run",
|
"test:unit": "vitest run",
|
||||||
"test:ui": "playwright test --ui",
|
"test:ui": "playwright test --ui",
|
||||||
"i18n-scan": "cd .. && node tools/i18n-scanner/index.js && node tools/i18n-scanner/report.js"
|
"i18n-scan": "cd .. && node tools/i18n-scanner/index.js && node tools/i18n-scanner/report.js"
|
||||||
@@ -43,6 +44,7 @@
|
|||||||
"zod": "^4.4.3"
|
"zod": "^4.4.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@biomejs/biome": "2.4.16",
|
||||||
"@playwright/test": "^1.60.0",
|
"@playwright/test": "^1.60.0",
|
||||||
"@testing-library/jest-dom": "^6.9.1",
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
"@testing-library/react": "^16.3.2",
|
"@testing-library/react": "^16.3.2",
|
||||||
@@ -52,8 +54,10 @@
|
|||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@types/react-router-dom": "^5.3.3",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
"@vitejs/plugin-react": "^6.0.1",
|
"@vitejs/plugin-react": "^6.0.1",
|
||||||
|
"@vitest/coverage-v8": "4.1.6",
|
||||||
"autoprefixer": "^10.5.0",
|
"autoprefixer": "^10.5.0",
|
||||||
"jsdom": "^28.1.0",
|
"jsdom": "^28.1.0",
|
||||||
|
"playwright": "1.60.0",
|
||||||
"postcss": "^8.5.14",
|
"postcss": "^8.5.14",
|
||||||
"tailwindcss": "^3.4.19",
|
"tailwindcss": "^3.4.19",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
|||||||
98
adminfront/pnpm-lock.yaml
generated
98
adminfront/pnpm-lock.yaml
generated
@@ -75,6 +75,9 @@ importers:
|
|||||||
specifier: ^4.4.3
|
specifier: ^4.4.3
|
||||||
version: 4.4.3
|
version: 4.4.3
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
'@biomejs/biome':
|
||||||
|
specifier: 2.4.16
|
||||||
|
version: 2.4.16
|
||||||
'@playwright/test':
|
'@playwright/test':
|
||||||
specifier: ^1.60.0
|
specifier: ^1.60.0
|
||||||
version: 1.60.0
|
version: 1.60.0
|
||||||
@@ -108,6 +111,9 @@ importers:
|
|||||||
jsdom:
|
jsdom:
|
||||||
specifier: ^28.1.0
|
specifier: ^28.1.0
|
||||||
version: 28.1.0
|
version: 28.1.0
|
||||||
|
playwright:
|
||||||
|
specifier: 1.60.0
|
||||||
|
version: 1.60.0
|
||||||
postcss:
|
postcss:
|
||||||
specifier: ^8.5.14
|
specifier: ^8.5.14
|
||||||
version: 8.5.14
|
version: 8.5.14
|
||||||
@@ -165,6 +171,63 @@ packages:
|
|||||||
resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==}
|
resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
|
'@biomejs/biome@2.4.16':
|
||||||
|
resolution: {integrity: sha512-x9ajFh1zChVybCiM3TN6OD4phAqLgtPZjFrZF+aTMYCPjwBO+k529TX7PPsAqtGNLeV4UgzwQnowEgS7bGmzcA==}
|
||||||
|
engines: {node: '>=14.21.3'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
'@biomejs/cli-darwin-arm64@2.4.16':
|
||||||
|
resolution: {integrity: sha512-wxPvu4XOA85YJk9ixSWUmq/QBHbid85BISbOAqqBM/5xQpPk9ayjk5375tOlSC0BeCwNSbPFafQBm+vBumXq0A==}
|
||||||
|
engines: {node: '>=14.21.3'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@biomejs/cli-darwin-x64@2.4.16':
|
||||||
|
resolution: {integrity: sha512-xFCqGPwYusQJp4N4NJLi1XJiZqjwFdjhT+KqtNy+Ug3qgfczqnTa6MSDvxJF6TkuDLoYJItMapz6tAf7kCekFw==}
|
||||||
|
engines: {node: '>=14.21.3'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@biomejs/cli-linux-arm64-musl@2.4.16':
|
||||||
|
resolution: {integrity: sha512-oYxnW0ARfJkr72ezzF2OR8N/rtkgLUQeYtF8cFhVswbknHxtTcmzSsanVJP8yQKnGpGpc2ck6c5zLvHahL6Cbg==}
|
||||||
|
engines: {node: '>=14.21.3'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
|
'@biomejs/cli-linux-arm64@2.4.16':
|
||||||
|
resolution: {integrity: sha512-2kFb4//jxfZaP6D+Rj5VkHkxgyD9EoRAVBEQb8PKRv+s4NO2zYNJKXFaJmK1CmhufJOWEfpHKaRbOja7qjmdhQ==}
|
||||||
|
engines: {node: '>=14.21.3'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
|
'@biomejs/cli-linux-x64-musl@2.4.16':
|
||||||
|
resolution: {integrity: sha512-iHDS+MCM65DPqWGu+ECC3uoALyj2H7F4nVUPxIPjz/PIl94EUu+EDfGZDzFP+NY1EOPVt9NQvwFqq7HdMmowdg==}
|
||||||
|
engines: {node: '>=14.21.3'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
|
'@biomejs/cli-linux-x64@2.4.16':
|
||||||
|
resolution: {integrity: sha512-NbcBbi/nJqn5baae6wqRXdS7Gadf2uRpehSh6vMSYpG8OhkXl/Xg8aorWrJ+9VWqAT5ml90alLvorkpMW0nBwQ==}
|
||||||
|
engines: {node: '>=14.21.3'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
|
'@biomejs/cli-win32-arm64@2.4.16':
|
||||||
|
resolution: {integrity: sha512-0rgImMsNb5v/chhkIFe3wu7PEFClS6RBAYUijGL9UsYN3PanSaoK24HSSuSJb1pYbYYVjzAyZTl3gtjJ84BM8A==}
|
||||||
|
engines: {node: '>=14.21.3'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@biomejs/cli-win32-x64@2.4.16':
|
||||||
|
resolution: {integrity: sha512-Kp85jgoBHa05gix6UIRjfCDiUV3w/8VIdZ247VyyO2gEjaw12WEVhdIjlxp/AMzXxqxQwbxNTDVZ3Mwd2RG5rw==}
|
||||||
|
engines: {node: '>=14.21.3'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
'@bramus/specificity@2.4.2':
|
'@bramus/specificity@2.4.2':
|
||||||
resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==}
|
resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -1936,6 +1999,41 @@ snapshots:
|
|||||||
|
|
||||||
'@babel/runtime@7.29.2': {}
|
'@babel/runtime@7.29.2': {}
|
||||||
|
|
||||||
|
'@biomejs/biome@2.4.16':
|
||||||
|
optionalDependencies:
|
||||||
|
'@biomejs/cli-darwin-arm64': 2.4.16
|
||||||
|
'@biomejs/cli-darwin-x64': 2.4.16
|
||||||
|
'@biomejs/cli-linux-arm64': 2.4.16
|
||||||
|
'@biomejs/cli-linux-arm64-musl': 2.4.16
|
||||||
|
'@biomejs/cli-linux-x64': 2.4.16
|
||||||
|
'@biomejs/cli-linux-x64-musl': 2.4.16
|
||||||
|
'@biomejs/cli-win32-arm64': 2.4.16
|
||||||
|
'@biomejs/cli-win32-x64': 2.4.16
|
||||||
|
|
||||||
|
'@biomejs/cli-darwin-arm64@2.4.16':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@biomejs/cli-darwin-x64@2.4.16':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@biomejs/cli-linux-arm64-musl@2.4.16':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@biomejs/cli-linux-arm64@2.4.16':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@biomejs/cli-linux-x64-musl@2.4.16':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@biomejs/cli-linux-x64@2.4.16':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@biomejs/cli-win32-arm64@2.4.16':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@biomejs/cli-win32-x64@2.4.16':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@bramus/specificity@2.4.2':
|
'@bramus/specificity@2.4.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
css-tree: 3.2.1
|
css-tree: 3.2.1
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
allowBuilds:
|
allowBuilds:
|
||||||
|
'@biomejs/biome': true
|
||||||
esbuild: false
|
esbuild: false
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { createServer } from "node:http";
|
|
||||||
import { readFile, stat } from "node:fs/promises";
|
import { readFile, stat } from "node:fs/promises";
|
||||||
|
import { createServer } from "node:http";
|
||||||
import { extname, join, normalize, resolve } from "node:path";
|
import { extname, join, normalize, resolve } from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
@@ -24,7 +24,9 @@ const contentTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function getContentType(filePath) {
|
function getContentType(filePath) {
|
||||||
return contentTypes[extname(filePath).toLowerCase()] ?? "application/octet-stream";
|
return (
|
||||||
|
contentTypes[extname(filePath).toLowerCase()] ?? "application/octet-stream"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendJson(res, statusCode, body) {
|
function sendJson(res, statusCode, body) {
|
||||||
@@ -132,7 +134,10 @@ async function serveStatic(req, res, pathname) {
|
|||||||
|
|
||||||
createServer(async (req, res) => {
|
createServer(async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
const url = new URL(
|
||||||
|
req.url ?? "/",
|
||||||
|
`http://${req.headers.host ?? "localhost"}`,
|
||||||
|
);
|
||||||
const { pathname, search } = url;
|
const { pathname, search } = url;
|
||||||
|
|
||||||
if (pathname === "/api" || pathname.startsWith("/api/")) {
|
if (pathname === "/api" || pathname.startsWith("/api/")) {
|
||||||
@@ -149,5 +154,7 @@ createServer(async (req, res) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).listen(port, host, () => {
|
}).listen(port, host, () => {
|
||||||
console.log(`Adminfront production server listening on http://${host}:${port}`);
|
console.log(
|
||||||
|
`Adminfront production server listening on http://${host}:${port}`,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { createBrowserRouter } from "react-router-dom";
|
|
||||||
import type { RouteObject } from "react-router-dom";
|
import type { RouteObject } from "react-router-dom";
|
||||||
|
import { createBrowserRouter } from "react-router-dom";
|
||||||
import AppLayout from "../components/layout/AppLayout";
|
import AppLayout from "../components/layout/AppLayout";
|
||||||
import ApiKeyCreatePage from "../features/api-keys/ApiKeyCreatePage";
|
import ApiKeyCreatePage from "../features/api-keys/ApiKeyCreatePage";
|
||||||
import ApiKeyListPage from "../features/api-keys/ApiKeyListPage";
|
import ApiKeyListPage from "../features/api-keys/ApiKeyListPage";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { render, screen, fireEvent } from "@testing-library/react";
|
import { fireEvent, render, screen } from "@testing-library/react";
|
||||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import LanguageSelector from "./LanguageSelector";
|
import LanguageSelector from "./LanguageSelector";
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { LOCALE_STORAGE_KEY } from "../../../../common/core/i18n";
|
import { LOCALE_STORAGE_KEY } from "../../../../common/core/i18n";
|
||||||
import { t } from "../../lib/i18n";
|
import { t } from "../../lib/i18n";
|
||||||
|
|
||||||
const SUPPORTED_LOCALES = ["ko", "en"] as const;
|
const SUPPORTED_LOCALES = ["ko", "en"] as const;
|
||||||
|
|
||||||
type Locale = (typeof SUPPORTED_LOCALES)[number];
|
type Locale = (typeof SUPPORTED_LOCALES)[number];
|
||||||
|
|||||||
@@ -22,13 +22,13 @@ import { useAuth } from "react-oidc-context";
|
|||||||
import { NavLink, Outlet, useLocation, useNavigate } from "react-router-dom";
|
import { NavLink, Outlet, useLocation, useNavigate } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
AppSidebar,
|
AppSidebar,
|
||||||
type ShellSidebarNavItem,
|
|
||||||
type ShellTranslator,
|
|
||||||
applyShellTheme,
|
applyShellTheme,
|
||||||
buildShellProfileSummary,
|
buildShellProfileSummary,
|
||||||
buildShellSessionStatus,
|
buildShellSessionStatus,
|
||||||
readShellSessionExpiryEnabled,
|
readShellSessionExpiryEnabled,
|
||||||
readShellTheme,
|
readShellTheme,
|
||||||
|
type ShellSidebarNavItem,
|
||||||
|
type ShellTranslator,
|
||||||
shellLayoutClasses,
|
shellLayoutClasses,
|
||||||
writeShellSessionExpiryEnabled,
|
writeShellSessionExpiryEnabled,
|
||||||
} from "../../../../common/shell";
|
} from "../../../../common/shell";
|
||||||
@@ -310,13 +310,16 @@ function AppLayout() {
|
|||||||
window.addEventListener(DEV_ROLE_CHANGED_EVENT, rerenderDevelopmentShell);
|
window.addEventListener(DEV_ROLE_CHANGED_EVENT, rerenderDevelopmentShell);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener(LOCALE_CHANGED_EVENT, rerenderDevelopmentShell);
|
window.removeEventListener(
|
||||||
|
LOCALE_CHANGED_EVENT,
|
||||||
|
rerenderDevelopmentShell,
|
||||||
|
);
|
||||||
window.removeEventListener(
|
window.removeEventListener(
|
||||||
DEV_ROLE_CHANGED_EVENT,
|
DEV_ROLE_CHANGED_EVENT,
|
||||||
rerenderDevelopmentShell,
|
rerenderDevelopmentShell,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}, [isDevelopmentRuntime]);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleClickOutside = (event: MouseEvent) => {
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
@@ -429,7 +432,6 @@ function AppLayout() {
|
|||||||
auth.isAuthenticated,
|
auth.isAuthenticated,
|
||||||
auth.isLoading,
|
auth.isLoading,
|
||||||
auth.user?.expires_at,
|
auth.user?.expires_at,
|
||||||
isDevelopmentRuntime,
|
|
||||||
isSessionExpiryEnabled,
|
isSessionExpiryEnabled,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -668,7 +670,10 @@ function AppLayout() {
|
|||||||
<div className="flex items-center justify-between gap-3">
|
<div className="flex items-center justify-between gap-3">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-foreground">
|
<p className="text-sm font-medium text-foreground">
|
||||||
{t("ui.shell.session.auto_extend", "세션 만료 관리")}
|
{t(
|
||||||
|
"ui.shell.session.auto_extend",
|
||||||
|
"세션 만료 관리",
|
||||||
|
)}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
{isSessionExpiryEnabled ? (
|
{isSessionExpiryEnabled ? (
|
||||||
@@ -677,7 +682,10 @@ function AppLayout() {
|
|||||||
t={t}
|
t={t}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
t("ui.shell.session.disabled", "세션 만료 비활성화")
|
t(
|
||||||
|
"ui.shell.session.disabled",
|
||||||
|
"세션 만료 비활성화",
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -44,4 +44,4 @@ const AvatarFallback = React.forwardRef<
|
|||||||
));
|
));
|
||||||
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
|
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
|
||||||
|
|
||||||
export { Avatar, AvatarImage, AvatarFallback };
|
export { Avatar, AvatarFallback, AvatarImage };
|
||||||
|
|||||||
@@ -50,9 +50,9 @@ function CardFooter({
|
|||||||
|
|
||||||
export {
|
export {
|
||||||
Card,
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardFooter,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
CardDescription,
|
|
||||||
CardContent,
|
|
||||||
CardFooter,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -144,18 +144,20 @@ const DialogClose = React.forwardRef<HTMLButtonElement, DialogTriggerProps>(
|
|||||||
DialogClose.displayName = "DialogClose";
|
DialogClose.displayName = "DialogClose";
|
||||||
|
|
||||||
const DialogOverlay = React.forwardRef<
|
const DialogOverlay = React.forwardRef<
|
||||||
HTMLDivElement,
|
HTMLButtonElement,
|
||||||
React.HTMLAttributes<HTMLDivElement>
|
React.ButtonHTMLAttributes<HTMLButtonElement>
|
||||||
>(({ className, onMouseDown, ...props }, ref) => {
|
>(({ className, onMouseDown, ...props }, ref) => {
|
||||||
const { setOpen } = useDialogContext("DialogOverlay");
|
const { setOpen } = useDialogContext("DialogOverlay");
|
||||||
return (
|
return (
|
||||||
<div
|
<button
|
||||||
|
type="button"
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
"fixed inset-0 z-50 border-0 bg-black/80 p-0 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
data-state="open"
|
data-state="open"
|
||||||
|
aria-label="Close dialog"
|
||||||
onMouseDown={composeEventHandlers(onMouseDown, (event) => {
|
onMouseDown={composeEventHandlers(onMouseDown, (event) => {
|
||||||
if (event.target === event.currentTarget) {
|
if (event.target === event.currentTarget) {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
@@ -273,13 +275,13 @@ DialogDescription.displayName = "DialogDescription";
|
|||||||
|
|
||||||
export {
|
export {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogPortal,
|
|
||||||
DialogOverlay,
|
|
||||||
DialogClose,
|
DialogClose,
|
||||||
DialogTrigger,
|
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogHeader,
|
|
||||||
DialogFooter,
|
|
||||||
DialogTitle,
|
|
||||||
DialogDescription,
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogOverlay,
|
||||||
|
DialogPortal,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -183,18 +183,18 @@ DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
|
|||||||
|
|
||||||
export {
|
export {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuTrigger,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuCheckboxItem,
|
DropdownMenuCheckboxItem,
|
||||||
DropdownMenuRadioItem,
|
DropdownMenuContent,
|
||||||
|
DropdownMenuGroup,
|
||||||
|
DropdownMenuItem,
|
||||||
DropdownMenuLabel,
|
DropdownMenuLabel,
|
||||||
|
DropdownMenuPortal,
|
||||||
|
DropdownMenuRadioGroup,
|
||||||
|
DropdownMenuRadioItem,
|
||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuShortcut,
|
DropdownMenuShortcut,
|
||||||
DropdownMenuGroup,
|
|
||||||
DropdownMenuPortal,
|
|
||||||
DropdownMenuSub,
|
DropdownMenuSub,
|
||||||
DropdownMenuSubContent,
|
DropdownMenuSubContent,
|
||||||
DropdownMenuSubTrigger,
|
DropdownMenuSubTrigger,
|
||||||
DropdownMenuRadioGroup,
|
DropdownMenuTrigger,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -146,13 +146,13 @@ SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
|
|||||||
|
|
||||||
export {
|
export {
|
||||||
Select,
|
Select,
|
||||||
SelectGroup,
|
|
||||||
SelectValue,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectContent,
|
SelectContent,
|
||||||
SelectLabel,
|
SelectGroup,
|
||||||
SelectItem,
|
SelectItem,
|
||||||
SelectSeparator,
|
SelectLabel,
|
||||||
SelectScrollUpButton,
|
|
||||||
SelectScrollDownButton,
|
SelectScrollDownButton,
|
||||||
|
SelectScrollUpButton,
|
||||||
|
SelectSeparator,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -92,11 +92,11 @@ TableCaption.displayName = "TableCaption";
|
|||||||
|
|
||||||
export {
|
export {
|
||||||
Table,
|
Table,
|
||||||
TableHeader,
|
|
||||||
TableBody,
|
TableBody,
|
||||||
|
TableCaption,
|
||||||
|
TableCell,
|
||||||
TableFooter,
|
TableFooter,
|
||||||
TableHead,
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
TableCell,
|
|
||||||
TableCaption,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -84,4 +84,4 @@ const TabsContent = React.forwardRef<
|
|||||||
});
|
});
|
||||||
TabsContent.displayName = "TabsContent";
|
TabsContent.displayName = "TabsContent";
|
||||||
|
|
||||||
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
export { Tabs, TabsContent, TabsList, TabsTrigger };
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ describe("AuthPage", () => {
|
|||||||
|
|
||||||
expect(screen.getByText("Auth Guard")).toBeInTheDocument();
|
expect(screen.getByText("Auth Guard")).toBeInTheDocument();
|
||||||
expect(screen.getByText("ReBAC permission checker")).toBeInTheDocument();
|
expect(screen.getByText("ReBAC permission checker")).toBeInTheDocument();
|
||||||
expect(screen.getByRole("button", { name: "Check permission" })).toBeInTheDocument();
|
expect(
|
||||||
|
screen.getByRole("button", { name: "Check permission" }),
|
||||||
|
).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -42,7 +42,9 @@ describe("LoginPage", () => {
|
|||||||
it("shows an actionable error instead of starting PKCE when WebCrypto is unavailable", async () => {
|
it("shows an actionable error instead of starting PKCE when WebCrypto is unavailable", async () => {
|
||||||
renderLoginPage("/login?returnTo=%2F");
|
renderLoginPage("/login?returnTo=%2F");
|
||||||
|
|
||||||
await userEvent.click(screen.getByRole("button", { name: /SSO 계정으로 로그인/i }));
|
await userEvent.click(
|
||||||
|
screen.getByRole("button", { name: /SSO 계정으로 로그인/i }),
|
||||||
|
);
|
||||||
|
|
||||||
expect(mockSigninRedirect).not.toHaveBeenCalled();
|
expect(mockSigninRedirect).not.toHaveBeenCalled();
|
||||||
expect(screen.getByRole("alert")).toHaveTextContent(
|
expect(screen.getByRole("alert")).toHaveTextContent(
|
||||||
@@ -61,7 +63,9 @@ describe("LoginPage", () => {
|
|||||||
});
|
});
|
||||||
renderLoginPage("/login?returnTo=%2Fusers%3Fpage%3D2");
|
renderLoginPage("/login?returnTo=%2Fusers%3Fpage%3D2");
|
||||||
|
|
||||||
await userEvent.click(screen.getByRole("button", { name: /SSO 계정으로 로그인/i }));
|
await userEvent.click(
|
||||||
|
screen.getByRole("button", { name: /SSO 계정으로 로그인/i }),
|
||||||
|
);
|
||||||
|
|
||||||
expect(mockSigninRedirect).toHaveBeenCalledWith({
|
expect(mockSigninRedirect).toHaveBeenCalledWith({
|
||||||
state: {
|
state: {
|
||||||
|
|||||||
@@ -48,10 +48,7 @@ function PermissionChecker() {
|
|||||||
<Card className="border-primary/20 bg-[var(--color-panel)]">
|
<Card className="border-primary/20 bg-[var(--color-panel)]">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-lg font-bold">
|
<CardTitle className="text-lg font-bold">
|
||||||
{t(
|
{t("ui.admin.auth_guard.checker.title", "ReBAC permission checker")}
|
||||||
"ui.admin.auth_guard.checker.title",
|
|
||||||
"ReBAC permission checker",
|
|
||||||
)}
|
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
{t(
|
{t(
|
||||||
@@ -92,7 +89,9 @@ function PermissionChecker() {
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>{t("ui.admin.auth_guard.checker.relation", "Relation")}</Label>
|
<Label>
|
||||||
|
{t("ui.admin.auth_guard.checker.relation", "Relation")}
|
||||||
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
placeholder={t(
|
placeholder={t(
|
||||||
"ui.admin.auth_guard.checker.relation_placeholder",
|
"ui.admin.auth_guard.checker.relation_placeholder",
|
||||||
@@ -103,7 +102,9 @@ function PermissionChecker() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>{t("ui.admin.auth_guard.checker.object_id", "Object ID")}</Label>
|
<Label>
|
||||||
|
{t("ui.admin.auth_guard.checker.object_id", "Object ID")}
|
||||||
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
placeholder={t(
|
placeholder={t(
|
||||||
"ui.admin.auth_guard.checker.object_id_placeholder",
|
"ui.admin.auth_guard.checker.object_id_placeholder",
|
||||||
@@ -115,10 +116,7 @@ function PermissionChecker() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>
|
<Label>
|
||||||
{t(
|
{t("ui.admin.auth_guard.checker.subject", "Subject (User:ID)")}
|
||||||
"ui.admin.auth_guard.checker.subject",
|
|
||||||
"Subject (User:ID)",
|
|
||||||
)}
|
|
||||||
</Label>
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
placeholder={t(
|
placeholder={t(
|
||||||
@@ -155,10 +153,7 @@ function PermissionChecker() {
|
|||||||
<>
|
<>
|
||||||
<CheckCircle2 size={48} />
|
<CheckCircle2 size={48} />
|
||||||
<div className="text-lg font-bold">
|
<div className="text-lg font-bold">
|
||||||
{t(
|
{t("ui.admin.auth_guard.checker.allowed", "Access ALLOWED")}
|
||||||
"ui.admin.auth_guard.checker.allowed",
|
|
||||||
"Access ALLOWED",
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<p className="text-center text-sm opacity-80">
|
<p className="text-center text-sm opacity-80">
|
||||||
{t(
|
{t(
|
||||||
@@ -171,10 +166,7 @@ function PermissionChecker() {
|
|||||||
<>
|
<>
|
||||||
<XCircle size={48} />
|
<XCircle size={48} />
|
||||||
<div className="text-lg font-bold">
|
<div className="text-lg font-bold">
|
||||||
{t(
|
{t("ui.admin.auth_guard.checker.denied", "Access DENIED")}
|
||||||
"ui.admin.auth_guard.checker.denied",
|
|
||||||
"Access DENIED",
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<p className="text-center text-sm opacity-80">
|
<p className="text-center text-sm opacity-80">
|
||||||
{t(
|
{t(
|
||||||
|
|||||||
@@ -175,16 +175,16 @@ describe("DataIntegrityPage", () => {
|
|||||||
window.localStorage.setItem("locale", "en");
|
window.localStorage.setItem("locale", "en");
|
||||||
renderPage();
|
renderPage();
|
||||||
|
|
||||||
expect(
|
expect(await screen.findByText("Data Integrity Check")).toBeInTheDocument();
|
||||||
await screen.findByText("Data Integrity Check"),
|
|
||||||
).toBeInTheDocument();
|
|
||||||
expect(
|
expect(
|
||||||
await screen.findByText(
|
await screen.findByText(
|
||||||
"Review integrity status and inspect checks across the admin data model.",
|
"Review integrity status and inspect checks across the admin data model.",
|
||||||
),
|
),
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
expect(await screen.findByText("Tenant integrity")).toBeInTheDocument();
|
expect(await screen.findByText("Tenant integrity")).toBeInTheDocument();
|
||||||
expect(await screen.findByText("Duplicate tenant slug")).toBeInTheDocument();
|
expect(
|
||||||
|
await screen.findByText("Duplicate tenant slug"),
|
||||||
|
).toBeInTheDocument();
|
||||||
expect(
|
expect(
|
||||||
await screen.findByText(
|
await screen.findByText(
|
||||||
"Checks duplicate active tenant slugs using LOWER(TRIM(slug)).",
|
"Checks duplicate active tenant slugs using LOWER(TRIM(slug)).",
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ import { Button } from "../../components/ui/button";
|
|||||||
import {
|
import {
|
||||||
type DataIntegrityCheck,
|
type DataIntegrityCheck,
|
||||||
type DataIntegrityStatus,
|
type DataIntegrityStatus,
|
||||||
type OrphanUserLoginID,
|
|
||||||
deleteOrphanUserLoginIDs,
|
deleteOrphanUserLoginIDs,
|
||||||
fetchDataIntegrityReport,
|
fetchDataIntegrityReport,
|
||||||
fetchOrphanUserLoginIDs,
|
fetchOrphanUserLoginIDs,
|
||||||
|
type OrphanUserLoginID,
|
||||||
} from "../../lib/adminApi";
|
} from "../../lib/adminApi";
|
||||||
import { t } from "../../lib/i18n";
|
import { t } from "../../lib/i18n";
|
||||||
import { getAdminDateLocale } from "../../lib/locale";
|
import { getAdminDateLocale } from "../../lib/locale";
|
||||||
|
|||||||
@@ -17,13 +17,13 @@ import {
|
|||||||
import { RoleGuard } from "../../components/auth/RoleGuard";
|
import { RoleGuard } from "../../components/auth/RoleGuard";
|
||||||
import {
|
import {
|
||||||
type DataIntegrityStatus,
|
type DataIntegrityStatus,
|
||||||
type RPUsageDailyMetric,
|
|
||||||
type RPUsagePeriod,
|
|
||||||
type TenantSummary,
|
|
||||||
fetchAdminOverviewStats,
|
fetchAdminOverviewStats,
|
||||||
fetchAdminRPUsageDaily,
|
fetchAdminRPUsageDaily,
|
||||||
fetchAllTenants,
|
fetchAllTenants,
|
||||||
fetchDataIntegrityReport,
|
fetchDataIntegrityReport,
|
||||||
|
type RPUsageDailyMetric,
|
||||||
|
type RPUsagePeriod,
|
||||||
|
type TenantSummary,
|
||||||
} from "../../lib/adminApi";
|
} from "../../lib/adminApi";
|
||||||
import { t } from "../../lib/i18n";
|
import { t } from "../../lib/i18n";
|
||||||
|
|
||||||
@@ -203,10 +203,7 @@ function IntegrityOverviewSummary() {
|
|||||||
<AlertTriangle size={18} className="text-amber-600" />
|
<AlertTriangle size={18} className="text-amber-600" />
|
||||||
)}
|
)}
|
||||||
<h3 className="text-lg font-bold flex items-center gap-2">
|
<h3 className="text-lg font-bold flex items-center gap-2">
|
||||||
{t(
|
{t("ui.admin.integrity.summary.title", "정합성 최종 검증")}
|
||||||
"ui.admin.integrity.summary.title",
|
|
||||||
"정합성 최종 검증",
|
|
||||||
)}
|
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap items-center gap-3 text-sm">
|
<div className="flex flex-wrap items-center gap-3 text-sm">
|
||||||
@@ -466,7 +463,7 @@ function GlobalOverviewPage() {
|
|||||||
const metric = (value: number | undefined) =>
|
const metric = (value: number | undefined) =>
|
||||||
value === undefined ? "-" : value.toLocaleString();
|
value === undefined ? "-" : value.toLocaleString();
|
||||||
const periodControls = (
|
const periodControls = (
|
||||||
<div className="flex h-8 items-center gap-1" aria-label="집계 단위">
|
<fieldset className="flex h-8 items-center gap-1" aria-label="집계 단위">
|
||||||
{[
|
{[
|
||||||
["day", t("ui.common.chart.period.day", "일")],
|
["day", t("ui.common.chart.period.day", "일")],
|
||||||
["week", t("ui.common.chart.period.week", "주")],
|
["week", t("ui.common.chart.period.week", "주")],
|
||||||
@@ -486,7 +483,7 @@ function GlobalOverviewPage() {
|
|||||||
{label}
|
{label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</fieldset>
|
||||||
);
|
);
|
||||||
const chartFilters = (
|
const chartFilters = (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -61,17 +61,13 @@ describe("UserProjectionPage", () => {
|
|||||||
it("renders projection status for super_admin", async () => {
|
it("renders projection status for super_admin", async () => {
|
||||||
renderPage();
|
renderPage();
|
||||||
|
|
||||||
expect(
|
expect(await screen.findByText("사용자 동기화 관리")).toBeInTheDocument();
|
||||||
await screen.findByText("사용자 동기화 관리"),
|
|
||||||
).toBeInTheDocument();
|
|
||||||
expect(
|
expect(
|
||||||
await screen.findByText(
|
await screen.findByText(
|
||||||
"Kratos 사용자 read model을 확인하고 동기화 상태를 갱신합니다.",
|
"Kratos 사용자 read model을 확인하고 동기화 상태를 갱신합니다.",
|
||||||
),
|
),
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
expect(
|
expect(await screen.findByText("Kratos 사용자 동기화")).toBeInTheDocument();
|
||||||
await screen.findByText("Kratos 사용자 동기화"),
|
|
||||||
).toBeInTheDocument();
|
|
||||||
expect(screen.getByText("준비됨")).toBeInTheDocument();
|
expect(screen.getByText("준비됨")).toBeInTheDocument();
|
||||||
expect(screen.getByText("152")).toBeInTheDocument();
|
expect(screen.getByText("152")).toBeInTheDocument();
|
||||||
expect(fetchUserProjectionStatus).toHaveBeenCalled();
|
expect(fetchUserProjectionStatus).toHaveBeenCalled();
|
||||||
@@ -100,9 +96,7 @@ describe("UserProjectionPage", () => {
|
|||||||
renderPage();
|
renderPage();
|
||||||
|
|
||||||
expect(await screen.findByText("접근 권한이 없습니다")).toBeInTheDocument();
|
expect(await screen.findByText("접근 권한이 없습니다")).toBeInTheDocument();
|
||||||
expect(
|
expect(screen.queryByText("사용자 동기화 관리")).not.toBeInTheDocument();
|
||||||
screen.queryByText("사용자 동기화 관리"),
|
|
||||||
).not.toBeInTheDocument();
|
|
||||||
expect(fetchUserProjectionStatus).not.toHaveBeenCalled();
|
expect(fetchUserProjectionStatus).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -133,10 +133,7 @@ function UserProjectionContent() {
|
|||||||
disabled={isWorking}
|
disabled={isWorking}
|
||||||
>
|
>
|
||||||
<RotateCcw size={16} />
|
<RotateCcw size={16} />
|
||||||
{t(
|
{t("ui.admin.user_projection.actions.reset", "Reset and rebuild")}
|
||||||
"ui.admin.user_projection.actions.reset",
|
|
||||||
"Reset and rebuild",
|
|
||||||
)}
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@@ -230,10 +227,7 @@ function UserProjectionContent() {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<dt className="text-sm text-muted-foreground">
|
<dt className="text-sm text-muted-foreground">
|
||||||
{t(
|
{t("ui.admin.user_projection.summary.updated_at", "Updated at")}
|
||||||
"ui.admin.user_projection.summary.updated_at",
|
|
||||||
"Updated at",
|
|
||||||
)}
|
|
||||||
</dt>
|
</dt>
|
||||||
<dd className="mt-1 text-sm">
|
<dd className="mt-1 text-sm">
|
||||||
{formatDateTime(data?.updatedAt)}
|
{formatDateTime(data?.updatedAt)}
|
||||||
@@ -258,22 +252,19 @@ export default function UserProjectionPage() {
|
|||||||
<RoleGuard
|
<RoleGuard
|
||||||
roles={["super_admin"]}
|
roles={["super_admin"]}
|
||||||
fallback={
|
fallback={
|
||||||
<main className="p-6 md:p-8">
|
<main className="p-6 md:p-8">
|
||||||
<section className="rounded-lg border border-border bg-card p-5">
|
<section className="rounded-lg border border-border bg-card p-5">
|
||||||
<h2 className="text-lg font-semibold">
|
<h2 className="text-lg font-semibold">
|
||||||
{t(
|
{t("ui.admin.user_projection.forbidden.title", "Access denied")}
|
||||||
"ui.admin.user_projection.forbidden.title",
|
</h2>
|
||||||
"Access denied",
|
<p className="mt-2 text-sm text-muted-foreground">
|
||||||
)}
|
{t(
|
||||||
</h2>
|
"msg.admin.user_projection.forbidden.description",
|
||||||
<p className="mt-2 text-sm text-muted-foreground">
|
"This screen is only available to super_admin users.",
|
||||||
{t(
|
)}
|
||||||
"msg.admin.user_projection.forbidden.description",
|
</p>
|
||||||
"This screen is only available to super_admin users.",
|
</section>
|
||||||
)}
|
</main>
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<UserProjectionContent />
|
<UserProjectionContent />
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import {
|
|||||||
DialogContent,
|
DialogContent,
|
||||||
DialogDescription,
|
DialogDescription,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTrigger,
|
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
} from "../../../components/ui/dialog";
|
} from "../../../components/ui/dialog";
|
||||||
import { Label } from "../../../components/ui/label";
|
import { Label } from "../../../components/ui/label";
|
||||||
import type { TenantSummary } from "../../../lib/adminApi";
|
import type { TenantSummary } from "../../../lib/adminApi";
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ import {
|
|||||||
} from "../../../components/ui/table";
|
} from "../../../components/ui/table";
|
||||||
import { toast } from "../../../components/ui/use-toast";
|
import { toast } from "../../../components/ui/use-toast";
|
||||||
import {
|
import {
|
||||||
type TenantAdmin,
|
|
||||||
addTenantAdmin,
|
addTenantAdmin,
|
||||||
addTenantOwner,
|
addTenantOwner,
|
||||||
fetchTenantAdmins,
|
fetchTenantAdmins,
|
||||||
@@ -49,6 +48,7 @@ import {
|
|||||||
fetchUsers,
|
fetchUsers,
|
||||||
removeTenantAdmin,
|
removeTenantAdmin,
|
||||||
removeTenantOwner,
|
removeTenantOwner,
|
||||||
|
type TenantAdmin,
|
||||||
} from "../../../lib/adminApi";
|
} from "../../../lib/adminApi";
|
||||||
import { t } from "../../../lib/i18n";
|
import { t } from "../../../lib/i18n";
|
||||||
|
|
||||||
@@ -69,15 +69,14 @@ export function TenantAdminsAndOwnersTab() {
|
|||||||
const auth = useAuth();
|
const auth = useAuth();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const currentUserId = auth.user?.profile.sub;
|
const currentUserId = auth.user?.profile.sub;
|
||||||
const { tenantId } = useParams<{ tenantId: string }>();
|
const { tenantId: tenantIdParam } = useParams<{ tenantId: string }>();
|
||||||
|
const tenantId = tenantIdParam ?? "";
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
const [dialogMode, setDialogMode] = useState<DialogMode | null>(null);
|
const [dialogMode, setDialogMode] = useState<DialogMode | null>(null);
|
||||||
const [pendingOwners, setPendingOwners] = useState<TenantAdmin[]>([]);
|
const [pendingOwners, setPendingOwners] = useState<TenantAdmin[]>([]);
|
||||||
const [pendingAdmins, setPendingAdmins] = useState<TenantAdmin[]>([]);
|
const [pendingAdmins, setPendingAdmins] = useState<TenantAdmin[]>([]);
|
||||||
|
|
||||||
if (!tenantId) return null;
|
|
||||||
|
|
||||||
const ownersQuery = useQuery({
|
const ownersQuery = useQuery({
|
||||||
queryKey: ["tenant-owners", tenantId],
|
queryKey: ["tenant-owners", tenantId],
|
||||||
queryFn: () => fetchTenantOwners(tenantId),
|
queryFn: () => fetchTenantOwners(tenantId),
|
||||||
@@ -339,6 +338,8 @@ export function TenantAdminsAndOwnersTab() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!tenantId) return null;
|
||||||
|
|
||||||
const serverOwners = ownersQuery.data || [];
|
const serverOwners = ownersQuery.data || [];
|
||||||
const serverAdmins = adminsQuery.data || [];
|
const serverAdmins = adminsQuery.data || [];
|
||||||
const currentOwners = mergePendingMembers(serverOwners, pendingOwners);
|
const currentOwners = mergePendingMembers(serverOwners, pendingOwners);
|
||||||
|
|||||||
@@ -19,15 +19,15 @@ import { t } from "../../../lib/i18n";
|
|||||||
import { DomainTagInput } from "../components/DomainTagInput";
|
import { DomainTagInput } from "../components/DomainTagInput";
|
||||||
import { ParentTenantSelector } from "../components/ParentTenantSelector";
|
import { ParentTenantSelector } from "../components/ParentTenantSelector";
|
||||||
import {
|
import {
|
||||||
type ServerDomainConflict,
|
|
||||||
formatDomainConflictMessage,
|
formatDomainConflictMessage,
|
||||||
|
type ServerDomainConflict,
|
||||||
} from "../utils/domainTags";
|
} from "../utils/domainTags";
|
||||||
import {
|
import {
|
||||||
|
mergeTenantOrgConfig,
|
||||||
ORG_UNIT_TYPE_OPTIONS,
|
ORG_UNIT_TYPE_OPTIONS,
|
||||||
|
shouldAllowHanmacOrgConfig,
|
||||||
TENANT_VISIBILITY_OPTIONS,
|
TENANT_VISIBILITY_OPTIONS,
|
||||||
type TenantVisibility,
|
type TenantVisibility,
|
||||||
mergeTenantOrgConfig,
|
|
||||||
shouldAllowHanmacOrgConfig,
|
|
||||||
} from "../utils/orgConfig";
|
} from "../utils/orgConfig";
|
||||||
|
|
||||||
type AdminFrontTestHooks = {
|
type AdminFrontTestHooks = {
|
||||||
|
|||||||
@@ -53,13 +53,13 @@ import {
|
|||||||
} from "../../../components/ui/table";
|
} from "../../../components/ui/table";
|
||||||
import { toast } from "../../../components/ui/use-toast";
|
import { toast } from "../../../components/ui/use-toast";
|
||||||
import {
|
import {
|
||||||
type GroupSummary,
|
|
||||||
addGroupMember,
|
addGroupMember,
|
||||||
createGroup,
|
createGroup,
|
||||||
deleteGroup,
|
deleteGroup,
|
||||||
fetchGroups,
|
fetchGroups,
|
||||||
fetchTenant,
|
fetchTenant,
|
||||||
fetchUsers,
|
fetchUsers,
|
||||||
|
type GroupSummary,
|
||||||
removeGroupMember,
|
removeGroupMember,
|
||||||
} from "../../../lib/adminApi";
|
} from "../../../lib/adminApi";
|
||||||
import { t } from "../../../lib/i18n";
|
import { t } from "../../../lib/i18n";
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import {
|
|||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
|
import { PageHeader } from "../../../../../common/core/components/page";
|
||||||
import {
|
import {
|
||||||
SortableTableHead,
|
SortableTableHead,
|
||||||
sortableTableHeadBaseClassName,
|
sortableTableHeadBaseClassName,
|
||||||
@@ -38,7 +39,6 @@ import {
|
|||||||
sortItems,
|
sortItems,
|
||||||
toggleSort,
|
toggleSort,
|
||||||
} from "../../../../../common/core/utils";
|
} from "../../../../../common/core/utils";
|
||||||
import { PageHeader } from "../../../../../common/core/components/page";
|
|
||||||
import {
|
import {
|
||||||
commonStickyTableHeaderClass,
|
commonStickyTableHeaderClass,
|
||||||
commonTableShellClass,
|
commonTableShellClass,
|
||||||
@@ -92,18 +92,18 @@ import {
|
|||||||
import { toast } from "../../../components/ui/use-toast";
|
import { toast } from "../../../components/ui/use-toast";
|
||||||
import type { UserProfileResponse } from "../../../lib/adminApi";
|
import type { UserProfileResponse } from "../../../lib/adminApi";
|
||||||
import {
|
import {
|
||||||
type TenantSummary,
|
|
||||||
deleteTenant,
|
deleteTenant,
|
||||||
deleteTenantsBulk,
|
deleteTenantsBulk,
|
||||||
exportTenantsCSV,
|
exportTenantsCSV,
|
||||||
fetchMe,
|
fetchMe,
|
||||||
fetchTenants,
|
fetchTenants,
|
||||||
importTenantsCSV,
|
importTenantsCSV,
|
||||||
|
type TenantSummary,
|
||||||
updateTenant,
|
updateTenant,
|
||||||
} from "../../../lib/adminApi";
|
} from "../../../lib/adminApi";
|
||||||
import { t } from "../../../lib/i18n";
|
import { t } from "../../../lib/i18n";
|
||||||
import { normalizeAdminRole } from "../../../lib/roles";
|
import { normalizeAdminRole } from "../../../lib/roles";
|
||||||
import { type TenantNode, buildTenantFullTree } from "../../../lib/tenantTree";
|
import { buildTenantFullTree, type TenantNode } from "../../../lib/tenantTree";
|
||||||
import {
|
import {
|
||||||
buildAuthenticatedOrgChartTenantPickerUrl,
|
buildAuthenticatedOrgChartTenantPickerUrl,
|
||||||
filterNonHanmacFamilyTenants,
|
filterNonHanmacFamilyTenants,
|
||||||
@@ -112,20 +112,20 @@ import {
|
|||||||
} from "../../users/orgChartPicker";
|
} from "../../users/orgChartPicker";
|
||||||
import { isSeedTenant } from "../utils/protectedTenants";
|
import { isSeedTenant } from "../utils/protectedTenants";
|
||||||
import {
|
import {
|
||||||
type TenantImportPreviewRow,
|
|
||||||
type TenantImportResolution,
|
|
||||||
buildTenantImportParentOptionGroups,
|
buildTenantImportParentOptionGroups,
|
||||||
buildTenantImportPreview,
|
buildTenantImportPreview,
|
||||||
inferTenantImportRootParentSlug,
|
inferTenantImportRootParentSlug,
|
||||||
parseTenantCSV,
|
parseTenantCSV,
|
||||||
serializeTenantImportCSV,
|
serializeTenantImportCSV,
|
||||||
|
type TenantImportPreviewRow,
|
||||||
|
type TenantImportResolution,
|
||||||
} from "../utils/tenantCsvImport";
|
} from "../utils/tenantCsvImport";
|
||||||
import {
|
import {
|
||||||
type TenantViewMode,
|
|
||||||
type TenantViewRow,
|
|
||||||
filterTenantsByScope,
|
filterTenantsByScope,
|
||||||
getTenantViewRows,
|
getTenantViewRows,
|
||||||
resolveTenantSelectionIds,
|
resolveTenantSelectionIds,
|
||||||
|
type TenantViewMode,
|
||||||
|
type TenantViewRow,
|
||||||
tenantMatchesListSearch,
|
tenantMatchesListSearch,
|
||||||
} from "./tenantListView";
|
} from "./tenantListView";
|
||||||
|
|
||||||
@@ -453,30 +453,6 @@ function TenantListPage() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (
|
|
||||||
profile &&
|
|
||||||
profileRole !== "super_admin" &&
|
|
||||||
profileRole !== "tenant_admin"
|
|
||||||
) {
|
|
||||||
return (
|
|
||||||
<div className="flex h-[50vh] flex-col items-center justify-center space-y-4">
|
|
||||||
<h3 className="text-lg font-bold">
|
|
||||||
{t("msg.admin.common.forbidden", "접근 권한이 없습니다.")}
|
|
||||||
</h3>
|
|
||||||
<Button onClick={() => navigate("/")}>
|
|
||||||
{t("ui.common.go_home", "홈으로 이동")}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
profileRole === "tenant_admin" &&
|
|
||||||
(profile?.manageableTenants?.length ?? 0) <= 1
|
|
||||||
) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const errorMsg = (query.error as AxiosError<{ error?: string }>)?.response
|
const errorMsg = (query.error as AxiosError<{ error?: string }>)?.response
|
||||||
?.data?.error;
|
?.data?.error;
|
||||||
const fallbackError =
|
const fallbackError =
|
||||||
@@ -574,6 +550,30 @@ function TenantListPage() {
|
|||||||
return () => window.removeEventListener("message", onMessage);
|
return () => window.removeEventListener("message", onMessage);
|
||||||
}, [allTenants, scopePickerOpen]);
|
}, [allTenants, scopePickerOpen]);
|
||||||
|
|
||||||
|
if (
|
||||||
|
profile &&
|
||||||
|
profileRole !== "super_admin" &&
|
||||||
|
profileRole !== "tenant_admin"
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<div className="flex h-[50vh] flex-col items-center justify-center space-y-4">
|
||||||
|
<h3 className="text-lg font-bold">
|
||||||
|
{t("msg.admin.common.forbidden", "접근 권한이 없습니다.")}
|
||||||
|
</h3>
|
||||||
|
<Button onClick={() => navigate("/")}>
|
||||||
|
{t("ui.common.go_home", "홈으로 이동")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
profileRole === "tenant_admin" &&
|
||||||
|
(profile?.manageableTenants?.length ?? 0) <= 1
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const handleSelectAll = (checked: boolean) => {
|
const handleSelectAll = (checked: boolean) => {
|
||||||
if (checked) {
|
if (checked) {
|
||||||
setSelectedIds(deletableTenants.map((t) => t.id));
|
setSelectedIds(deletableTenants.map((t) => t.id));
|
||||||
|
|||||||
@@ -26,34 +26,30 @@ import { t } from "../../../lib/i18n";
|
|||||||
import { DomainTagInput } from "../components/DomainTagInput";
|
import { DomainTagInput } from "../components/DomainTagInput";
|
||||||
import { ParentTenantSelector } from "../components/ParentTenantSelector";
|
import { ParentTenantSelector } from "../components/ParentTenantSelector";
|
||||||
import {
|
import {
|
||||||
type ServerDomainConflict,
|
|
||||||
formatDomainConflictMessage,
|
formatDomainConflictMessage,
|
||||||
|
type ServerDomainConflict,
|
||||||
} from "../utils/domainTags";
|
} from "../utils/domainTags";
|
||||||
import {
|
import {
|
||||||
ORG_UNIT_TYPE_OPTIONS,
|
|
||||||
TENANT_VISIBILITY_OPTIONS,
|
|
||||||
type TenantVisibility,
|
|
||||||
mergeTenantOrgConfig,
|
mergeTenantOrgConfig,
|
||||||
|
ORG_UNIT_TYPE_OPTIONS,
|
||||||
readTenantOrgConfig,
|
readTenantOrgConfig,
|
||||||
removeTenantOrgConfig,
|
removeTenantOrgConfig,
|
||||||
shouldAllowHanmacOrgConfig,
|
shouldAllowHanmacOrgConfig,
|
||||||
|
TENANT_VISIBILITY_OPTIONS,
|
||||||
|
type TenantVisibility,
|
||||||
} from "../utils/orgConfig";
|
} from "../utils/orgConfig";
|
||||||
import { isSeedTenant } from "../utils/protectedTenants";
|
import { isSeedTenant } from "../utils/protectedTenants";
|
||||||
|
|
||||||
export function TenantProfilePage() {
|
export function TenantProfilePage() {
|
||||||
const { tenantId } = useParams<{ tenantId: string }>();
|
const { tenantId: tenantIdParam } = useParams<{ tenantId: string }>();
|
||||||
|
const tenantId = tenantIdParam ?? "";
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
if (!tenantId) {
|
|
||||||
return (
|
|
||||||
<div>{t("msg.admin.tenants.missing_id", "테넌트 ID가 없습니다.")}</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const tenantQuery = useQuery({
|
const tenantQuery = useQuery({
|
||||||
queryKey: ["tenant", tenantId],
|
queryKey: ["tenant", tenantId],
|
||||||
queryFn: () => fetchTenant(tenantId),
|
queryFn: () => fetchTenant(tenantId),
|
||||||
|
enabled: tenantId.length > 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
const parentQuery = useQuery({
|
const parentQuery = useQuery({
|
||||||
@@ -197,6 +193,12 @@ export function TenantProfilePage() {
|
|||||||
? isSeedTenant(tenantQuery.data)
|
? isSeedTenant(tenantQuery.data)
|
||||||
: false;
|
: false;
|
||||||
|
|
||||||
|
if (!tenantId) {
|
||||||
|
return (
|
||||||
|
<div>{t("msg.admin.tenants.missing_id", "테넌트 ID가 없습니다.")}</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const handleDelete = () => {
|
const handleDelete = () => {
|
||||||
if (isProtectedSeedTenant) {
|
if (isProtectedSeedTenant) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ import { fetchMe, fetchTenant, updateTenant } from "../../../lib/adminApi";
|
|||||||
import { t } from "../../../lib/i18n";
|
import { t } from "../../../lib/i18n";
|
||||||
import { normalizeAdminRole } from "../../../lib/roles";
|
import { normalizeAdminRole } from "../../../lib/roles";
|
||||||
import {
|
import {
|
||||||
type SchemaField,
|
|
||||||
createSchemaField,
|
createSchemaField,
|
||||||
isSchemaFieldType,
|
isSchemaFieldType,
|
||||||
normalizeSchemaField,
|
normalizeSchemaField,
|
||||||
|
type SchemaField,
|
||||||
} from "./tenantSchemaFields";
|
} from "./tenantSchemaFields";
|
||||||
|
|
||||||
export function TenantSchemaPage() {
|
export function TenantSchemaPage() {
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ import {
|
|||||||
} from "../../../components/ui/table";
|
} from "../../../components/ui/table";
|
||||||
import { toast } from "../../../components/ui/use-toast";
|
import { toast } from "../../../components/ui/use-toast";
|
||||||
import {
|
import {
|
||||||
type WorksmobileComparisonItem,
|
|
||||||
downloadWorksmobileInitialPasswordsCSV,
|
downloadWorksmobileInitialPasswordsCSV,
|
||||||
enqueueWorksmobileBackfillDryRun,
|
enqueueWorksmobileBackfillDryRun,
|
||||||
enqueueWorksmobileOrgUnitDelete,
|
enqueueWorksmobileOrgUnitDelete,
|
||||||
@@ -47,13 +46,10 @@ import {
|
|||||||
fetchWorksmobileComparison,
|
fetchWorksmobileComparison,
|
||||||
fetchWorksmobileOverview,
|
fetchWorksmobileOverview,
|
||||||
retryWorksmobileJob,
|
retryWorksmobileJob,
|
||||||
|
type WorksmobileComparisonItem,
|
||||||
} from "../../../lib/adminApi";
|
} from "../../../lib/adminApi";
|
||||||
import { t } from "../../../lib/i18n";
|
import { t } from "../../../lib/i18n";
|
||||||
import {
|
import {
|
||||||
type WorksmobileComparisonColumnKey,
|
|
||||||
type WorksmobileComparisonColumnVisibility,
|
|
||||||
type WorksmobileComparisonFilter,
|
|
||||||
type WorksmobileComparisonSummary,
|
|
||||||
buildWorksmobilePasswordManageUrl,
|
buildWorksmobilePasswordManageUrl,
|
||||||
canOpenWorksmobilePasswordManage,
|
canOpenWorksmobilePasswordManage,
|
||||||
canSelectWorksmobileRow,
|
canSelectWorksmobileRow,
|
||||||
@@ -71,6 +67,10 @@ import {
|
|||||||
getWorksmobileSelectedActionIds,
|
getWorksmobileSelectedActionIds,
|
||||||
getWorksmobileSelectedWorksOnlyOrgUnitIds,
|
getWorksmobileSelectedWorksOnlyOrgUnitIds,
|
||||||
summarizeWorksmobileComparison,
|
summarizeWorksmobileComparison,
|
||||||
|
type WorksmobileComparisonColumnKey,
|
||||||
|
type WorksmobileComparisonColumnVisibility,
|
||||||
|
type WorksmobileComparisonFilter,
|
||||||
|
type WorksmobileComparisonSummary,
|
||||||
} from "./worksmobileComparison";
|
} from "./worksmobileComparison";
|
||||||
|
|
||||||
export function TenantWorksmobilePage() {
|
export function TenantWorksmobilePage() {
|
||||||
@@ -1196,13 +1196,7 @@ function ComparisonTable({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ComparisonDomainCell({
|
function ComparisonDomainCell({ name, id }: { name?: string; id?: number }) {
|
||||||
name,
|
|
||||||
id,
|
|
||||||
}: {
|
|
||||||
name?: string;
|
|
||||||
id?: number;
|
|
||||||
}) {
|
|
||||||
if (!name && !id) {
|
if (!name && !id) {
|
||||||
return <span className="text-muted-foreground">-</span>;
|
return <span className="text-muted-foreground">-</span>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { TenantSummary } from "../../../lib/adminApi";
|
import type { TenantSummary } from "../../../lib/adminApi";
|
||||||
import { type TenantNode, buildTenantFullTree } from "../../../lib/tenantTree";
|
import { buildTenantFullTree, type TenantNode } from "../../../lib/tenantTree";
|
||||||
|
|
||||||
export type TenantViewMode = "tree" | "table";
|
export type TenantViewMode = "tree" | "table";
|
||||||
export type TenantViewRow = TenantNode & { depth: number };
|
export type TenantViewRow = TenantNode & { depth: number };
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import type { TenantSummary } from "../../../lib/adminApi";
|
import type { TenantSummary } from "../../../lib/adminApi";
|
||||||
import {
|
import {
|
||||||
ORG_UNIT_TYPE_OPTIONS,
|
|
||||||
mergeTenantOrgConfig,
|
mergeTenantOrgConfig,
|
||||||
|
ORG_UNIT_TYPE_OPTIONS,
|
||||||
readTenantOrgConfig,
|
readTenantOrgConfig,
|
||||||
shouldAllowHanmacOrgConfig,
|
shouldAllowHanmacOrgConfig,
|
||||||
} from "./orgConfig";
|
} from "./orgConfig";
|
||||||
|
|||||||
@@ -150,7 +150,6 @@ describe("tenantCsvImport", () => {
|
|||||||
expect(csv).not.toContain("local-tenant-id");
|
expect(csv).not.toContain("local-tenant-id");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it("preserves source tenant_id when a create resolution does not override it", () => {
|
it("preserves source tenant_id when a create resolution does not override it", () => {
|
||||||
const exportedTenantId = "11111111-2222-4333-8444-555555555555";
|
const exportedTenantId = "11111111-2222-4333-8444-555555555555";
|
||||||
const rows = parseTenantCSV(
|
const rows = parseTenantCSV(
|
||||||
|
|||||||
@@ -403,7 +403,6 @@ function createTenantImportId() {
|
|||||||
.padEnd(12, "0")}`;
|
.padEnd(12, "0")}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function isUUIDLikeTenantId(value: string) {
|
function isUUIDLikeTenantId(value: string) {
|
||||||
return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(
|
return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(
|
||||||
value,
|
value,
|
||||||
@@ -596,7 +595,7 @@ function slugify(value: string) {
|
|||||||
지원: "support",
|
지원: "support",
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = value.trim();
|
const result = value.trim();
|
||||||
|
|
||||||
// 1. 전체 매칭 확인
|
// 1. 전체 매칭 확인
|
||||||
if (commonMappings[result]) {
|
if (commonMappings[result]) {
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
} from "../../../components/ui/table";
|
} from "../../../components/ui/table";
|
||||||
import {
|
import {
|
||||||
type TenantSummary,
|
|
||||||
fetchAllTenants,
|
fetchAllTenants,
|
||||||
fetchGroups,
|
fetchGroups,
|
||||||
|
type TenantSummary,
|
||||||
} from "../../../lib/adminApi";
|
} from "../../../lib/adminApi";
|
||||||
|
|
||||||
export default function GlobalUserGroupListPage() {
|
export default function GlobalUserGroupListPage() {
|
||||||
|
|||||||
@@ -70,17 +70,17 @@ import {
|
|||||||
} from "../../../components/ui/tabs";
|
} from "../../../components/ui/tabs";
|
||||||
import { toast } from "../../../components/ui/use-toast";
|
import { toast } from "../../../components/ui/use-toast";
|
||||||
import {
|
import {
|
||||||
type TenantSummary,
|
|
||||||
type UserSummary,
|
|
||||||
createUser,
|
createUser,
|
||||||
exportTenantsCSV,
|
exportTenantsCSV,
|
||||||
fetchAllTenants,
|
fetchAllTenants,
|
||||||
fetchUsers,
|
fetchUsers,
|
||||||
|
type TenantSummary,
|
||||||
|
type UserSummary,
|
||||||
updateTenant,
|
updateTenant,
|
||||||
updateUser,
|
updateUser,
|
||||||
} from "../../../lib/adminApi";
|
} from "../../../lib/adminApi";
|
||||||
import { t } from "../../../lib/i18n";
|
import { t } from "../../../lib/i18n";
|
||||||
import { type TenantNode, buildTenantFullTree } from "../../../lib/tenantTree";
|
import { buildTenantFullTree, type TenantNode } from "../../../lib/tenantTree";
|
||||||
|
|
||||||
// --- Icons & Helpers ---
|
// --- Icons & Helpers ---
|
||||||
const getTenantIcon = (type?: string) => {
|
const getTenantIcon = (type?: string) => {
|
||||||
@@ -482,8 +482,10 @@ function TenantUserGroupsTab() {
|
|||||||
mutationFn: ({
|
mutationFn: ({
|
||||||
id,
|
id,
|
||||||
parentId,
|
parentId,
|
||||||
}: { id: string; parentId: string | undefined }) =>
|
}: {
|
||||||
updateTenant(id, { parentId: parentId || "" }),
|
id: string;
|
||||||
|
parentId: string | undefined;
|
||||||
|
}) => updateTenant(id, { parentId: parentId || "" }),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ["tenants-full-tree-v2"] });
|
queryClient.invalidateQueries({ queryKey: ["tenants-full-tree-v2"] });
|
||||||
toast.success(
|
toast.success(
|
||||||
|
|||||||
@@ -574,9 +574,9 @@ export function UserGroupDetailPage() {
|
|||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
) : (
|
) : (
|
||||||
groupRoles.map((role, idx) => (
|
groupRoles.map((role) => (
|
||||||
<TableRow
|
<TableRow
|
||||||
key={`${role.tenantId}-${role.relation}-${idx}`}
|
key={`${role.tenantId}-${role.relation}`}
|
||||||
className="hover:bg-muted/30 transition-colors"
|
className="hover:bg-muted/30 transition-colors"
|
||||||
>
|
>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
|
|||||||
@@ -38,21 +38,21 @@ import {
|
|||||||
TabsTrigger,
|
TabsTrigger,
|
||||||
} from "../../components/ui/tabs";
|
} from "../../components/ui/tabs";
|
||||||
import {
|
import {
|
||||||
type TenantSummary,
|
|
||||||
type UserAppointment,
|
|
||||||
type UserCreateRequest,
|
|
||||||
type UserCreateResponse,
|
|
||||||
createUser,
|
createUser,
|
||||||
fetchAllTenants,
|
fetchAllTenants,
|
||||||
fetchMe,
|
fetchMe,
|
||||||
fetchTenant,
|
fetchTenant,
|
||||||
|
type TenantSummary,
|
||||||
|
type UserAppointment,
|
||||||
|
type UserCreateRequest,
|
||||||
|
type UserCreateResponse,
|
||||||
} from "../../lib/adminApi";
|
} from "../../lib/adminApi";
|
||||||
import { t } from "../../lib/i18n";
|
import { t } from "../../lib/i18n";
|
||||||
import { isSuperAdminRole } from "../../lib/roles";
|
import { isSuperAdminRole } from "../../lib/roles";
|
||||||
import {
|
import {
|
||||||
type OrgChartTenantSelection,
|
|
||||||
buildAuthenticatedOrgChartTenantPickerUrl,
|
buildAuthenticatedOrgChartTenantPickerUrl,
|
||||||
filterNonHanmacFamilyTenants,
|
filterNonHanmacFamilyTenants,
|
||||||
|
type OrgChartTenantSelection,
|
||||||
parseOrgChartTenantSelection,
|
parseOrgChartTenantSelection,
|
||||||
} from "./orgChartPicker";
|
} from "./orgChartPicker";
|
||||||
import type { UserSchemaField } from "./userSchemaFields";
|
import type { UserSchemaField } from "./userSchemaFields";
|
||||||
|
|||||||
@@ -59,10 +59,8 @@ import {
|
|||||||
TabsTrigger,
|
TabsTrigger,
|
||||||
} from "../../components/ui/tabs";
|
} from "../../components/ui/tabs";
|
||||||
import { toast } from "../../components/ui/use-toast";
|
import { toast } from "../../components/ui/use-toast";
|
||||||
|
import type { PasswordPolicyResponse } from "../../lib/adminApi";
|
||||||
import {
|
import {
|
||||||
type TenantSummary,
|
|
||||||
type UserAppointment,
|
|
||||||
type UserUpdateRequest,
|
|
||||||
deleteUser,
|
deleteUser,
|
||||||
fetchAllTenants,
|
fetchAllTenants,
|
||||||
fetchMe,
|
fetchMe,
|
||||||
@@ -70,18 +68,20 @@ import {
|
|||||||
fetchTenant,
|
fetchTenant,
|
||||||
fetchUser,
|
fetchUser,
|
||||||
fetchUserRpHistory,
|
fetchUserRpHistory,
|
||||||
|
type TenantSummary,
|
||||||
|
type UserAppointment,
|
||||||
|
type UserUpdateRequest,
|
||||||
updateUser,
|
updateUser,
|
||||||
} from "../../lib/adminApi";
|
} from "../../lib/adminApi";
|
||||||
import type { PasswordPolicyResponse } from "../../lib/adminApi";
|
|
||||||
import { t } from "../../lib/i18n";
|
import { t } from "../../lib/i18n";
|
||||||
import { normalizeAdminRole } from "../../lib/roles";
|
import { normalizeAdminRole } from "../../lib/roles";
|
||||||
import { generateSecurePassword } from "../../lib/utils";
|
import { generateSecurePassword } from "../../lib/utils";
|
||||||
import {
|
import {
|
||||||
type OrgChartTenantSelection,
|
|
||||||
buildAuthenticatedOrgChartTenantPickerUrl,
|
buildAuthenticatedOrgChartTenantPickerUrl,
|
||||||
filterNonHanmacFamilyTenants,
|
filterNonHanmacFamilyTenants,
|
||||||
isHanmacFamilyTenant,
|
isHanmacFamilyTenant,
|
||||||
isHanmacFamilyUser,
|
isHanmacFamilyUser,
|
||||||
|
type OrgChartTenantSelection,
|
||||||
parseOrgChartTenantSelection,
|
parseOrgChartTenantSelection,
|
||||||
} from "./orgChartPicker";
|
} from "./orgChartPicker";
|
||||||
import type { UserSchemaField } from "./userSchemaFields";
|
import type { UserSchemaField } from "./userSchemaFields";
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ const users = Array.from({ length: 200 }, (_, index) => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const fetchUsersMock = vi.hoisted(() => vi.fn());
|
const fetchUsersMock = vi.hoisted(() => vi.fn());
|
||||||
|
const searchRenderBudgetMs =
|
||||||
|
process.env.npm_lifecycle_event === "test:coverage" ? 500 : 200;
|
||||||
|
|
||||||
vi.mock("../../lib/i18n", () => createI18nMock());
|
vi.mock("../../lib/i18n", () => createI18nMock());
|
||||||
|
|
||||||
@@ -93,16 +95,21 @@ function renderUserListPage() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createDeferred<T>() {
|
||||||
|
let resolve: (value: T) => void = () => {};
|
||||||
|
const promise = new Promise<T>((promiseResolve) => {
|
||||||
|
resolve = promiseResolve;
|
||||||
|
});
|
||||||
|
|
||||||
|
return { promise, resolve };
|
||||||
|
}
|
||||||
|
|
||||||
describe("UserListPage search rendering", () => {
|
describe("UserListPage search rendering", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
selectRenderCounter.count = 0;
|
selectRenderCounter.count = 0;
|
||||||
fetchUsersMock.mockReset();
|
fetchUsersMock.mockReset();
|
||||||
fetchUsersMock.mockImplementation(
|
fetchUsersMock.mockImplementation(
|
||||||
async (
|
async (_limit: number, _offset: number, search?: string) => {
|
||||||
_limit: number,
|
|
||||||
_offset: number,
|
|
||||||
search?: string,
|
|
||||||
) => {
|
|
||||||
const normalizedSearch = search?.trim().toLowerCase();
|
const normalizedSearch = search?.trim().toLowerCase();
|
||||||
const items = normalizedSearch
|
const items = normalizedSearch
|
||||||
? users.filter((user) =>
|
? users.filter((user) =>
|
||||||
@@ -119,7 +126,7 @@ describe("UserListPage search rendering", () => {
|
|||||||
it("does not rerender user table controls while typing a draft search", async () => {
|
it("does not rerender user table controls while typing a draft search", async () => {
|
||||||
renderUserListPage();
|
renderUserListPage();
|
||||||
|
|
||||||
await screen.findByText("User 199");
|
await screen.findByText("User 0");
|
||||||
const searchInput = screen.getByPlaceholderText("이름 또는 이메일 검색...");
|
const searchInput = screen.getByPlaceholderText("이름 또는 이메일 검색...");
|
||||||
const renderCountBeforeTyping = selectRenderCounter.count;
|
const renderCountBeforeTyping = selectRenderCounter.count;
|
||||||
|
|
||||||
@@ -129,20 +136,57 @@ describe("UserListPage search rendering", () => {
|
|||||||
expect(selectRenderCounter.count).toBe(renderCountBeforeTyping);
|
expect(selectRenderCounter.count).toBe(renderCountBeforeTyping);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps rendered row controls below the full 200-user result set", async () => {
|
||||||
|
renderUserListPage();
|
||||||
|
|
||||||
|
await screen.findByText("User 0");
|
||||||
|
|
||||||
|
expect(screen.getAllByTestId(/^user-status-select-/).length).toBeLessThan(
|
||||||
|
200,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders compact vertically centered user table headers", async () => {
|
||||||
|
renderUserListPage();
|
||||||
|
|
||||||
|
await screen.findByText("User 0");
|
||||||
|
const nameHeader = screen.getByRole("columnheader", { name: /이름/ });
|
||||||
|
const content = nameHeader.firstElementChild;
|
||||||
|
|
||||||
|
expect(nameHeader).toHaveClass("h-9", "py-1", "align-middle", "text-xs");
|
||||||
|
expect(content).toHaveClass("flex", "h-full", "items-center");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("centers the initial loading message across the user table", async () => {
|
||||||
|
const deferred = createDeferred<{ items: typeof users; total: number }>();
|
||||||
|
fetchUsersMock.mockReturnValueOnce(deferred.promise);
|
||||||
|
|
||||||
|
renderUserListPage();
|
||||||
|
|
||||||
|
const loadingCell = await screen.findByTestId("user-table-loading-cell");
|
||||||
|
expect(loadingCell).toHaveClass(
|
||||||
|
"flex",
|
||||||
|
"items-center",
|
||||||
|
"justify-center",
|
||||||
|
"text-center",
|
||||||
|
);
|
||||||
|
expect(loadingCell).toHaveStyle({ gridColumn: "1 / -1" });
|
||||||
|
|
||||||
|
deferred.resolve({ items: users, total: users.length });
|
||||||
|
});
|
||||||
|
|
||||||
it("renders a 200-user search result update within 200ms after search submit", async () => {
|
it("renders a 200-user search result update within 200ms after search submit", async () => {
|
||||||
renderUserListPage();
|
renderUserListPage();
|
||||||
|
|
||||||
await screen.findByText("User 199");
|
await screen.findByText("User 0");
|
||||||
const searchInput = screen.getByPlaceholderText("이름 또는 이메일 검색...");
|
const searchInput = screen.getByPlaceholderText("이름 또는 이메일 검색...");
|
||||||
const startedAt = performance.now();
|
const startedAt = performance.now();
|
||||||
|
|
||||||
fireEvent.change(searchInput, { target: { value: "user 19" } });
|
fireEvent.change(searchInput, { target: { value: "user 19" } });
|
||||||
fireEvent.keyDown(searchInput, { key: "Enter" });
|
fireEvent.keyDown(searchInput, { key: "Enter" });
|
||||||
|
|
||||||
await screen.findByText("User 19");
|
expect(screen.getByText("User 19")).toBeInTheDocument();
|
||||||
await waitFor(() => {
|
expect(screen.queryByText("User 0")).not.toBeInTheDocument();
|
||||||
expect(screen.queryByText("User 0")).not.toBeInTheDocument();
|
expect(performance.now() - startedAt).toBeLessThan(searchRenderBudgetMs);
|
||||||
});
|
|
||||||
expect(performance.now() - startedAt).toBeLessThan(200);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||||
|
import {
|
||||||
|
observeElementRect,
|
||||||
|
type Rect,
|
||||||
|
useVirtualizer,
|
||||||
|
type Virtualizer,
|
||||||
|
} from "@tanstack/react-virtual";
|
||||||
import type { AxiosError } from "axios";
|
import type { AxiosError } from "axios";
|
||||||
import {
|
import {
|
||||||
ArrowDown,
|
ArrowDown,
|
||||||
@@ -7,7 +13,6 @@ import {
|
|||||||
ChevronDown,
|
ChevronDown,
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
Users,
|
|
||||||
Download,
|
Download,
|
||||||
FileDown,
|
FileDown,
|
||||||
FileSpreadsheet,
|
FileSpreadsheet,
|
||||||
@@ -19,13 +24,13 @@ import {
|
|||||||
ShieldCheck,
|
ShieldCheck,
|
||||||
Trash2,
|
Trash2,
|
||||||
Upload,
|
Upload,
|
||||||
|
Users,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
import { PageHeader } from "../../../../common/core/components/page";
|
import { PageHeader } from "../../../../common/core/components/page";
|
||||||
import {
|
import {
|
||||||
SortableTableHead,
|
SortableTableHead,
|
||||||
sortableTableHeadBaseClassName,
|
|
||||||
sortableTableHeaderClassName,
|
sortableTableHeaderClassName,
|
||||||
} from "../../../../common/core/components/sort";
|
} from "../../../../common/core/components/sort";
|
||||||
import {
|
import {
|
||||||
@@ -81,8 +86,6 @@ import {
|
|||||||
} from "../../components/ui/table";
|
} from "../../components/ui/table";
|
||||||
import { toast } from "../../components/ui/use-toast";
|
import { toast } from "../../components/ui/use-toast";
|
||||||
import {
|
import {
|
||||||
type TenantSummary,
|
|
||||||
type UserSummary,
|
|
||||||
bulkDeleteUsers,
|
bulkDeleteUsers,
|
||||||
bulkUpdateUsers,
|
bulkUpdateUsers,
|
||||||
deleteUser,
|
deleteUser,
|
||||||
@@ -91,13 +94,15 @@ import {
|
|||||||
fetchMe,
|
fetchMe,
|
||||||
fetchTenant,
|
fetchTenant,
|
||||||
fetchUsers,
|
fetchUsers,
|
||||||
|
type TenantSummary,
|
||||||
|
type UserSummary,
|
||||||
updateUser,
|
updateUser,
|
||||||
} from "../../lib/adminApi";
|
} from "../../lib/adminApi";
|
||||||
import { t } from "../../lib/i18n";
|
import { t } from "../../lib/i18n";
|
||||||
import { isSuperAdminRole } from "../../lib/roles";
|
import { isSuperAdminRole } from "../../lib/roles";
|
||||||
import {
|
import {
|
||||||
UserBulkUploadModal,
|
|
||||||
downloadUserTemplate,
|
downloadUserTemplate,
|
||||||
|
UserBulkUploadModal,
|
||||||
} from "./components/UserBulkUploadModal";
|
} from "./components/UserBulkUploadModal";
|
||||||
import {
|
import {
|
||||||
normalizeUserStatusValue,
|
normalizeUserStatusValue,
|
||||||
@@ -114,6 +119,23 @@ type UserSchemaField = {
|
|||||||
|
|
||||||
type UserSortKey = string;
|
type UserSortKey = string;
|
||||||
|
|
||||||
|
const USER_ROW_ESTIMATED_HEIGHT = 64;
|
||||||
|
const USER_ROW_OVERSCAN = 8;
|
||||||
|
const USER_TABLE_VIEWPORT_ESTIMATED_HEIGHT = 640;
|
||||||
|
const userFixedColumnWidths = [48, 160, 220, 160, 260, 170, 160, 220] as const;
|
||||||
|
const userMetadataColumnWidth = 160;
|
||||||
|
const userCreatedColumnWidth = 150;
|
||||||
|
type UserRowVirtualizer = Virtualizer<HTMLDivElement, HTMLTableRowElement>;
|
||||||
|
const userTableHeadClassName =
|
||||||
|
"h-9 px-3 py-1 text-xs leading-tight align-middle whitespace-nowrap";
|
||||||
|
const userTableHeadInteractiveClassName = `${userTableHeadClassName} cursor-pointer transition-colors hover:bg-muted/50`;
|
||||||
|
const userTableHeadContentClassName = "flex h-full items-center gap-1";
|
||||||
|
const userSortableTableHeadClassName =
|
||||||
|
"!h-9 !px-3 !py-1 leading-tight whitespace-nowrap";
|
||||||
|
const userSortableTableHeadContentClassName = "h-full items-center";
|
||||||
|
const userTableStateCellClassName =
|
||||||
|
"flex h-24 items-center justify-center p-0 text-center text-sm text-muted-foreground";
|
||||||
|
|
||||||
const bulkPermissionOptions = [
|
const bulkPermissionOptions = [
|
||||||
{
|
{
|
||||||
value: "super_admin",
|
value: "super_admin",
|
||||||
@@ -137,15 +159,24 @@ function userMatchesSearch(user: UserSummary, search: string) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return (
|
||||||
user.name,
|
user.name?.toLowerCase().includes(normalizedSearch) ||
|
||||||
user.email,
|
user.email?.toLowerCase().includes(normalizedSearch) ||
|
||||||
user.phone,
|
user.phone?.toLowerCase().includes(normalizedSearch) ||
|
||||||
user.id,
|
user.id?.toLowerCase().includes(normalizedSearch) ||
|
||||||
user.tenantSlug,
|
user.tenantSlug?.toLowerCase().includes(normalizedSearch) ||
|
||||||
user.tenant?.name,
|
user.tenant?.name?.toLowerCase().includes(normalizedSearch) ||
|
||||||
user.department,
|
user.department?.toLowerCase().includes(normalizedSearch) ||
|
||||||
].some((value) => value?.toLowerCase().includes(normalizedSearch));
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeUserTableRect(rect: Rect, fallbackWidth: number): Rect {
|
||||||
|
return {
|
||||||
|
width: rect.width > 0 ? rect.width : fallbackWidth,
|
||||||
|
height:
|
||||||
|
rect.height > 0 ? rect.height : USER_TABLE_VIEWPORT_ESTIMATED_HEIGHT,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserListSearchControlsProps = {
|
type UserListSearchControlsProps = {
|
||||||
@@ -253,6 +284,7 @@ function UserListPage() {
|
|||||||
const [sortConfig, setSortConfig] =
|
const [sortConfig, setSortConfig] =
|
||||||
React.useState<SortConfig<UserSortKey> | null>(null);
|
React.useState<SortConfig<UserSortKey> | null>(null);
|
||||||
const [bulkUploadOpen, setBulkUploadOpen] = React.useState(false);
|
const [bulkUploadOpen, setBulkUploadOpen] = React.useState(false);
|
||||||
|
const userTableViewportRef = React.useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const limit = 1000;
|
const limit = 1000;
|
||||||
const offset = (page - 1) * limit;
|
const offset = (page - 1) * limit;
|
||||||
@@ -417,8 +449,55 @@ function UserListPage() {
|
|||||||
[userSchema],
|
[userSchema],
|
||||||
);
|
);
|
||||||
const items = React.useMemo(() => {
|
const items = React.useMemo(() => {
|
||||||
|
if (!sortConfig) {
|
||||||
|
return rawItems;
|
||||||
|
}
|
||||||
|
|
||||||
return sortItems(rawItems, sortConfig, userSortResolvers);
|
return sortItems(rawItems, sortConfig, userSortResolvers);
|
||||||
}, [rawItems, sortConfig, userSortResolvers]);
|
}, [rawItems, sortConfig, userSortResolvers]);
|
||||||
|
const visibleUserSchemaFields = React.useMemo(
|
||||||
|
() => userSchema.filter((field) => visibleColumns[field.key] !== false),
|
||||||
|
[userSchema, visibleColumns],
|
||||||
|
);
|
||||||
|
const userTableColumnWidths = React.useMemo(
|
||||||
|
() => [
|
||||||
|
...userFixedColumnWidths,
|
||||||
|
...visibleUserSchemaFields.map(() => userMetadataColumnWidth),
|
||||||
|
userCreatedColumnWidth,
|
||||||
|
],
|
||||||
|
[visibleUserSchemaFields],
|
||||||
|
);
|
||||||
|
const userTableGridTemplateColumns = React.useMemo(
|
||||||
|
() => userTableColumnWidths.map((width) => `${width}px`).join(" "),
|
||||||
|
[userTableColumnWidths],
|
||||||
|
);
|
||||||
|
const userTableMinWidth = React.useMemo(
|
||||||
|
() => userTableColumnWidths.reduce((sum, width) => sum + width, 0),
|
||||||
|
[userTableColumnWidths],
|
||||||
|
);
|
||||||
|
const observeUserTableElementRect = React.useCallback(
|
||||||
|
(instance: UserRowVirtualizer, callback: (rect: Rect) => void) =>
|
||||||
|
observeElementRect(instance, (rect) => {
|
||||||
|
callback(normalizeUserTableRect(rect, userTableMinWidth));
|
||||||
|
}),
|
||||||
|
[userTableMinWidth],
|
||||||
|
);
|
||||||
|
const rowVirtualizer = useVirtualizer({
|
||||||
|
count: items.length,
|
||||||
|
getScrollElement: () => userTableViewportRef.current,
|
||||||
|
estimateSize: () => USER_ROW_ESTIMATED_HEIGHT,
|
||||||
|
measureElement: (element) =>
|
||||||
|
element.getBoundingClientRect().height || USER_ROW_ESTIMATED_HEIGHT,
|
||||||
|
observeElementRect: observeUserTableElementRect,
|
||||||
|
overscan: USER_ROW_OVERSCAN,
|
||||||
|
initialRect: {
|
||||||
|
width: userTableMinWidth,
|
||||||
|
height: USER_TABLE_VIEWPORT_ESTIMATED_HEIGHT,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const virtualRows = rowVirtualizer.getVirtualItems();
|
||||||
|
const shouldVirtualizeRows = !query.isLoading && items.length > 0;
|
||||||
|
const tableColumnCount = 9 + visibleUserSchemaFields.length;
|
||||||
|
|
||||||
const requestSort = (key: UserSortKey) => {
|
const requestSort = (key: UserSortKey) => {
|
||||||
setSortConfig((current) => toggleSort(current, key));
|
setSortConfig((current) => toggleSort(current, key));
|
||||||
@@ -715,82 +794,92 @@ function UserListPage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className={commonTableShellClass}>
|
<div className={commonTableShellClass}>
|
||||||
<div className={commonTableViewportClass}>
|
<div
|
||||||
<Table>
|
ref={userTableViewportRef}
|
||||||
|
data-testid="user-table-viewport"
|
||||||
|
className={commonTableViewportClass}
|
||||||
|
>
|
||||||
|
<Table style={{ display: "grid", minWidth: userTableMinWidth }}>
|
||||||
<TableHeader className={sortableTableHeaderClassName}>
|
<TableHeader className={sortableTableHeaderClassName}>
|
||||||
<TableRow>
|
<TableRow
|
||||||
<TableHead
|
style={{
|
||||||
className={`${sortableTableHeadBaseClassName} w-12`}
|
display: "grid",
|
||||||
>
|
gridTemplateColumns: userTableGridTemplateColumns,
|
||||||
<input
|
minWidth: userTableMinWidth,
|
||||||
type="checkbox"
|
}}
|
||||||
className="w-4 h-4 rounded border-gray-300 text-primary focus:ring-primary cursor-pointer"
|
>
|
||||||
checked={
|
<TableHead className={`${userTableHeadClassName} w-12`}>
|
||||||
items.length > 0 &&
|
<div className="flex h-full items-center justify-center">
|
||||||
selectedUserIds.length === items.length
|
<input
|
||||||
}
|
type="checkbox"
|
||||||
onChange={toggleSelectAll}
|
className="w-4 h-4 rounded border-gray-300 text-primary focus:ring-primary cursor-pointer"
|
||||||
/>
|
checked={
|
||||||
|
items.length > 0 &&
|
||||||
|
selectedUserIds.length === items.length
|
||||||
|
}
|
||||||
|
onChange={toggleSelectAll}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead
|
<TableHead
|
||||||
className="min-w-[120px] whitespace-nowrap cursor-pointer hover:bg-muted/50 transition-colors"
|
className={`${userTableHeadInteractiveClassName} min-w-[120px]`}
|
||||||
onClick={() => requestSort("name")}
|
onClick={() => requestSort("name")}
|
||||||
>
|
>
|
||||||
<div className="flex items-center">
|
<div className={userTableHeadContentClassName}>
|
||||||
{t("ui.admin.users.list.table.name", "이름")}
|
{t("ui.admin.users.list.table.name", "이름")}
|
||||||
{getSortIcon("name")}
|
{getSortIcon("name")}
|
||||||
</div>
|
</div>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead
|
<TableHead
|
||||||
className="min-w-[180px] whitespace-nowrap cursor-pointer hover:bg-muted/50 transition-colors"
|
className={`${userTableHeadInteractiveClassName} min-w-[180px]`}
|
||||||
onClick={() => requestSort("email")}
|
onClick={() => requestSort("email")}
|
||||||
>
|
>
|
||||||
<div className="flex items-center">
|
<div className={userTableHeadContentClassName}>
|
||||||
{t("ui.admin.users.list.table.email", "이메일")}
|
{t("ui.admin.users.list.table.email", "이메일")}
|
||||||
{getSortIcon("email")}
|
{getSortIcon("email")}
|
||||||
</div>
|
</div>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead
|
<TableHead
|
||||||
className="min-w-[140px] whitespace-nowrap cursor-pointer hover:bg-muted/50 transition-colors"
|
className={`${userTableHeadInteractiveClassName} min-w-[140px]`}
|
||||||
onClick={() => requestSort("phone")}
|
onClick={() => requestSort("phone")}
|
||||||
>
|
>
|
||||||
<div className="flex items-center">
|
<div className={userTableHeadContentClassName}>
|
||||||
{t("ui.admin.users.list.table.phone", "전화번호")}
|
{t("ui.admin.users.list.table.phone", "전화번호")}
|
||||||
{getSortIcon("phone")}
|
{getSortIcon("phone")}
|
||||||
</div>
|
</div>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead
|
<TableHead
|
||||||
className="min-w-[220px] whitespace-nowrap cursor-pointer hover:bg-muted/50 transition-colors"
|
className={`${userTableHeadInteractiveClassName} min-w-[220px]`}
|
||||||
onClick={() => requestSort("id")}
|
onClick={() => requestSort("id")}
|
||||||
>
|
>
|
||||||
<div className="flex items-center">
|
<div className={userTableHeadContentClassName}>
|
||||||
{t("ui.admin.users.list.table.id", "ID")}
|
{t("ui.admin.users.list.table.id", "ID")}
|
||||||
{getSortIcon("id")}
|
{getSortIcon("id")}
|
||||||
</div>
|
</div>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead
|
<TableHead
|
||||||
className="whitespace-nowrap cursor-pointer hover:bg-muted/50 transition-colors"
|
className={userTableHeadInteractiveClassName}
|
||||||
onClick={() => requestSort("status")}
|
onClick={() => requestSort("status")}
|
||||||
>
|
>
|
||||||
<div className="flex items-center">
|
<div className={userTableHeadContentClassName}>
|
||||||
{t("ui.admin.users.list.table.status", "STATUS")}
|
{t("ui.admin.users.list.table.status", "STATUS")}
|
||||||
{getSortIcon("status")}
|
{getSortIcon("status")}
|
||||||
</div>
|
</div>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead
|
<TableHead
|
||||||
className="whitespace-nowrap cursor-pointer hover:bg-muted/50 transition-colors"
|
className={userTableHeadInteractiveClassName}
|
||||||
onClick={() => requestSort("role")}
|
onClick={() => requestSort("role")}
|
||||||
>
|
>
|
||||||
<div className="flex items-center">
|
<div className={userTableHeadContentClassName}>
|
||||||
{t("ui.admin.users.list.table.role", "ROLE")}
|
{t("ui.admin.users.list.table.role", "ROLE")}
|
||||||
{getSortIcon("role")}
|
{getSortIcon("role")}
|
||||||
</div>
|
</div>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead
|
<TableHead
|
||||||
className="whitespace-nowrap cursor-pointer hover:bg-muted/50 transition-colors"
|
className={userTableHeadInteractiveClassName}
|
||||||
onClick={() => requestSort("tenant_dept")}
|
onClick={() => requestSort("tenant_dept")}
|
||||||
>
|
>
|
||||||
<div className="flex items-center">
|
<div className={userTableHeadContentClassName}>
|
||||||
{t(
|
{t(
|
||||||
"ui.admin.users.list.table.tenant_dept",
|
"ui.admin.users.list.table.tenant_dept",
|
||||||
"TENANT / DEPT",
|
"TENANT / DEPT",
|
||||||
@@ -799,21 +888,20 @@ function UserListPage() {
|
|||||||
</div>
|
</div>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
{/* Dynamic Columns from Schema */}
|
{/* Dynamic Columns from Schema */}
|
||||||
{userSchema.map(
|
{visibleUserSchemaFields.map((field) => (
|
||||||
(field) =>
|
<SortableTableHead
|
||||||
visibleColumns[field.key] !== false && (
|
key={field.key}
|
||||||
<SortableTableHead
|
className={userSortableTableHeadClassName}
|
||||||
key={field.key}
|
contentClassName={userSortableTableHeadContentClassName}
|
||||||
className="whitespace-nowrap"
|
label={field.label}
|
||||||
label={field.label}
|
onSort={requestSort}
|
||||||
onSort={requestSort}
|
sortConfig={sortConfig}
|
||||||
sortConfig={sortConfig}
|
sortKey={field.key}
|
||||||
sortKey={field.key}
|
/>
|
||||||
/>
|
))}
|
||||||
),
|
|
||||||
)}
|
|
||||||
<SortableTableHead
|
<SortableTableHead
|
||||||
className="whitespace-nowrap"
|
className={userSortableTableHeadClassName}
|
||||||
|
contentClassName={userSortableTableHeadContentClassName}
|
||||||
label={t("ui.admin.users.list.table.created", "CREATED")}
|
label={t("ui.admin.users.list.table.created", "CREATED")}
|
||||||
onSort={requestSort}
|
onSort={requestSort}
|
||||||
sortConfig={sortConfig}
|
sortConfig={sortConfig}
|
||||||
@@ -821,22 +909,51 @@ function UserListPage() {
|
|||||||
/>
|
/>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody
|
||||||
|
style={
|
||||||
|
shouldVirtualizeRows
|
||||||
|
? {
|
||||||
|
display: "grid",
|
||||||
|
height: `${rowVirtualizer.getTotalSize()}px`,
|
||||||
|
minWidth: userTableMinWidth,
|
||||||
|
position: "relative",
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
{query.isLoading && (
|
{query.isLoading && (
|
||||||
<TableRow>
|
<TableRow
|
||||||
|
data-testid="user-table-loading-row"
|
||||||
|
style={{
|
||||||
|
display: "grid",
|
||||||
|
gridTemplateColumns: userTableGridTemplateColumns,
|
||||||
|
minWidth: userTableMinWidth,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<TableCell
|
<TableCell
|
||||||
colSpan={7 + userSchema.length}
|
colSpan={tableColumnCount}
|
||||||
className="h-24 text-center"
|
data-testid="user-table-loading-cell"
|
||||||
|
className={userTableStateCellClassName}
|
||||||
|
style={{ gridColumn: "1 / -1" }}
|
||||||
>
|
>
|
||||||
{t("msg.common.loading", "로딩 중...")}
|
{t("msg.common.loading", "로딩 중...")}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
)}
|
)}
|
||||||
{!query.isLoading && items.length === 0 && (
|
{!query.isLoading && items.length === 0 && (
|
||||||
<TableRow>
|
<TableRow
|
||||||
|
data-testid="user-table-empty-row"
|
||||||
|
style={{
|
||||||
|
display: "grid",
|
||||||
|
gridTemplateColumns: userTableGridTemplateColumns,
|
||||||
|
minWidth: userTableMinWidth,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<TableCell
|
<TableCell
|
||||||
colSpan={7 + userSchema.length}
|
colSpan={tableColumnCount}
|
||||||
className="h-24 text-center"
|
data-testid="user-table-empty-cell"
|
||||||
|
className={userTableStateCellClassName}
|
||||||
|
style={{ gridColumn: "1 / -1" }}
|
||||||
>
|
>
|
||||||
{t(
|
{t(
|
||||||
"msg.admin.users.list.empty",
|
"msg.admin.users.list.empty",
|
||||||
@@ -845,145 +962,162 @@ function UserListPage() {
|
|||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
)}
|
)}
|
||||||
{items.map((user) => (
|
{shouldVirtualizeRows &&
|
||||||
<TableRow
|
virtualRows.map((virtualRow) => {
|
||||||
key={user.id}
|
const user = items[virtualRow.index];
|
||||||
className={
|
if (!user) return null;
|
||||||
selectedUserIds.includes(user.id) ? "bg-primary/5" : ""
|
|
||||||
}
|
return (
|
||||||
>
|
<TableRow
|
||||||
<TableCell>
|
key={user.id}
|
||||||
<input
|
data-index={virtualRow.index}
|
||||||
type="checkbox"
|
ref={rowVirtualizer.measureElement}
|
||||||
className="w-4 h-4 rounded border-gray-300 text-primary focus:ring-primary cursor-pointer disabled:opacity-30 disabled:cursor-not-allowed"
|
className={
|
||||||
checked={selectedUserIds.includes(user.id)}
|
selectedUserIds.includes(user.id)
|
||||||
onChange={() => toggleSelectUser(user.id)}
|
? "bg-primary/5"
|
||||||
disabled={user.id === profile?.id}
|
: ""
|
||||||
title={
|
|
||||||
user.id === profile?.id
|
|
||||||
? t(
|
|
||||||
"msg.admin.users.self_delete_blocked",
|
|
||||||
"본인 계정은 삭제할 수 없습니다.",
|
|
||||||
)
|
|
||||||
: undefined
|
|
||||||
}
|
}
|
||||||
/>
|
style={{
|
||||||
</TableCell>
|
display: "grid",
|
||||||
<TableCell>
|
gridTemplateColumns: userTableGridTemplateColumns,
|
||||||
<Link
|
height: `${virtualRow.size}px`,
|
||||||
to={`/users/${user.id}`}
|
minWidth: userTableMinWidth,
|
||||||
className="font-medium hover:underline text-primary truncate block max-w-[150px]"
|
position: "absolute",
|
||||||
title={user.name}
|
transform: `translateY(${virtualRow.start}px)`,
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{user.name}
|
<TableCell>
|
||||||
</Link>
|
<input
|
||||||
</TableCell>
|
type="checkbox"
|
||||||
<TableCell
|
className="w-4 h-4 rounded border-gray-300 text-primary focus:ring-primary cursor-pointer disabled:opacity-30 disabled:cursor-not-allowed"
|
||||||
className="text-sm text-muted-foreground truncate max-w-[200px]"
|
checked={selectedUserIds.includes(user.id)}
|
||||||
title={user.email}
|
onChange={() => toggleSelectUser(user.id)}
|
||||||
>
|
disabled={user.id === profile?.id}
|
||||||
{user.email}
|
title={
|
||||||
</TableCell>
|
user.id === profile?.id
|
||||||
<TableCell className="text-sm text-muted-foreground whitespace-nowrap">
|
? t(
|
||||||
{user.phone || "-"}
|
"msg.admin.users.self_delete_blocked",
|
||||||
</TableCell>
|
"본인 계정은 삭제할 수 없습니다.",
|
||||||
<TableCell
|
)
|
||||||
className="max-w-[220px] break-all font-mono text-xs text-muted-foreground"
|
: undefined
|
||||||
data-testid={`user-internal-id-${user.id}`}
|
}
|
||||||
>
|
/>
|
||||||
{user.id}
|
</TableCell>
|
||||||
</TableCell>
|
<TableCell>
|
||||||
<TableCell>
|
<Link
|
||||||
<Select
|
to={`/users/${user.id}`}
|
||||||
value={normalizeUserStatusValue(user.status)}
|
className="font-medium hover:underline text-primary truncate block max-w-[150px]"
|
||||||
onValueChange={(status) =>
|
title={user.name}
|
||||||
statusMutation.mutate({
|
>
|
||||||
userId: user.id,
|
{user.name}
|
||||||
status,
|
</Link>
|
||||||
})
|
</TableCell>
|
||||||
}
|
<TableCell
|
||||||
disabled={
|
className="text-sm text-muted-foreground truncate max-w-[200px]"
|
||||||
statusMutation.isPending || user.id === profile?.id
|
title={user.email}
|
||||||
}
|
|
||||||
>
|
|
||||||
<SelectTrigger
|
|
||||||
className="h-8 w-[150px] border-none bg-transparent hover:bg-muted/50 transition-colors px-0 font-medium"
|
|
||||||
aria-label={t(
|
|
||||||
"ui.admin.users.list.change_status",
|
|
||||||
"{{name}} 상태 변경",
|
|
||||||
{ name: user.name },
|
|
||||||
)}
|
|
||||||
data-testid={`user-status-select-${user.id}`}
|
|
||||||
>
|
>
|
||||||
<SelectValue />
|
{user.email}
|
||||||
</SelectTrigger>
|
</TableCell>
|
||||||
<SelectContent>
|
<TableCell className="text-sm text-muted-foreground whitespace-nowrap">
|
||||||
{userStatusValues.map((status) => (
|
{user.phone || "-"}
|
||||||
<SelectItem key={status} value={status}>
|
</TableCell>
|
||||||
{userStatusLabel(status)}
|
<TableCell
|
||||||
</SelectItem>
|
className="max-w-[220px] break-all font-mono text-xs text-muted-foreground"
|
||||||
))}
|
data-testid={`user-internal-id-${user.id}`}
|
||||||
</SelectContent>
|
>
|
||||||
</Select>
|
{user.id}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Select
|
<Select
|
||||||
value={assignableSystemRoleValue(user.role)}
|
value={normalizeUserStatusValue(user.status)}
|
||||||
onValueChange={(value) =>
|
onValueChange={(status) =>
|
||||||
bulkUpdateMutation.mutate({
|
statusMutation.mutate({
|
||||||
userIds: [user.id],
|
userId: user.id,
|
||||||
role: value,
|
status,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
disabled={
|
disabled={
|
||||||
bulkUpdateMutation.isPending ||
|
statusMutation.isPending ||
|
||||||
!isSuperAdminRole(profile?.role) ||
|
user.id === profile?.id
|
||||||
user.id === profile?.id
|
}
|
||||||
}
|
>
|
||||||
>
|
<SelectTrigger
|
||||||
<SelectTrigger className="h-8 w-[140px] border-none bg-transparent hover:bg-muted/50 transition-colors px-0 font-medium">
|
className="h-8 w-[150px] border-none bg-transparent hover:bg-muted/50 transition-colors px-0 font-medium"
|
||||||
<SelectValue />
|
aria-label={t(
|
||||||
</SelectTrigger>
|
"ui.admin.users.list.change_status",
|
||||||
<SelectContent>
|
"{{name}} 상태 변경",
|
||||||
{bulkPermissionOptions.map((option) => (
|
{ name: user.name },
|
||||||
<SelectItem
|
)}
|
||||||
key={option.value}
|
data-testid={`user-status-select-${user.id}`}
|
||||||
value={option.value}
|
|
||||||
>
|
>
|
||||||
{t(option.labelKey, option.fallback)}
|
<SelectValue />
|
||||||
</SelectItem>
|
</SelectTrigger>
|
||||||
))}
|
<SelectContent>
|
||||||
</SelectContent>
|
{userStatusValues.map((status) => (
|
||||||
</Select>
|
<SelectItem key={status} value={status}>
|
||||||
</TableCell>
|
{userStatusLabel(status)}
|
||||||
<TableCell>
|
</SelectItem>
|
||||||
<div className="flex flex-col gap-1">
|
))}
|
||||||
<span className="text-sm font-medium">
|
</SelectContent>
|
||||||
{user.tenant?.name ||
|
</Select>
|
||||||
user.tenantSlug ||
|
</TableCell>
|
||||||
t("ui.common.unassigned", "미배정")}
|
<TableCell>
|
||||||
</span>
|
<Select
|
||||||
{user.department && (
|
value={assignableSystemRoleValue(user.role)}
|
||||||
<span className="text-xs text-muted-foreground">
|
onValueChange={(value) =>
|
||||||
{user.department}
|
bulkUpdateMutation.mutate({
|
||||||
</span>
|
userIds: [user.id],
|
||||||
)}
|
role: value,
|
||||||
</div>
|
})
|
||||||
</TableCell>
|
}
|
||||||
{/* Dynamic Metadata Cells */}
|
disabled={
|
||||||
{userSchema.map(
|
bulkUpdateMutation.isPending ||
|
||||||
(field) =>
|
!isSuperAdminRole(profile?.role) ||
|
||||||
visibleColumns[field.key] !== false && (
|
user.id === profile?.id
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="h-8 w-[140px] border-none bg-transparent hover:bg-muted/50 transition-colors px-0 font-medium">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{bulkPermissionOptions.map((option) => (
|
||||||
|
<SelectItem
|
||||||
|
key={option.value}
|
||||||
|
value={option.value}
|
||||||
|
>
|
||||||
|
{t(option.labelKey, option.fallback)}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<span className="text-sm font-medium">
|
||||||
|
{user.tenant?.name ||
|
||||||
|
user.tenantSlug ||
|
||||||
|
t("ui.common.unassigned", "미배정")}
|
||||||
|
</span>
|
||||||
|
{user.department && (
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
{user.department}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
{/* Dynamic Metadata Cells */}
|
||||||
|
{visibleUserSchemaFields.map((field) => (
|
||||||
<TableCell key={field.key} className="text-sm">
|
<TableCell key={field.key} className="text-sm">
|
||||||
{String(user.metadata?.[field.key] ?? "-")}
|
{String(user.metadata?.[field.key] ?? "-")}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
),
|
))}
|
||||||
)}
|
<TableCell className="text-sm text-muted-foreground">
|
||||||
<TableCell className="text-sm text-muted-foreground">
|
{new Date(user.createdAt).toLocaleDateString()}
|
||||||
{new Date(user.createdAt).toLocaleDateString()}
|
</TableCell>
|
||||||
</TableCell>
|
</TableRow>
|
||||||
</TableRow>
|
);
|
||||||
))}
|
})}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ import { Input } from "../../../components/ui/input";
|
|||||||
import { ScrollArea } from "../../../components/ui/scroll-area";
|
import { ScrollArea } from "../../../components/ui/scroll-area";
|
||||||
import { toast } from "../../../components/ui/use-toast";
|
import { toast } from "../../../components/ui/use-toast";
|
||||||
import {
|
import {
|
||||||
type GroupSummary,
|
|
||||||
type TenantSummary,
|
|
||||||
type UserSummary,
|
|
||||||
bulkUpdateUsers,
|
bulkUpdateUsers,
|
||||||
fetchAllTenants,
|
fetchAllTenants,
|
||||||
fetchGroups,
|
fetchGroups,
|
||||||
|
type GroupSummary,
|
||||||
|
type TenantSummary,
|
||||||
|
type UserSummary,
|
||||||
} from "../../../lib/adminApi";
|
} from "../../../lib/adminApi";
|
||||||
import { t } from "../../../lib/i18n";
|
import { t } from "../../../lib/i18n";
|
||||||
|
|
||||||
|
|||||||
@@ -30,17 +30,17 @@ import {
|
|||||||
} from "../../../lib/adminApi";
|
} from "../../../lib/adminApi";
|
||||||
import { t } from "../../../lib/i18n";
|
import { t } from "../../../lib/i18n";
|
||||||
import {
|
import {
|
||||||
|
buildTenantImportPreview,
|
||||||
type TenantCSVRow,
|
type TenantCSVRow,
|
||||||
type TenantImportPreviewRow,
|
type TenantImportPreviewRow,
|
||||||
buildTenantImportPreview,
|
|
||||||
} from "../../tenants/utils/tenantCsvImport";
|
} from "../../tenants/utils/tenantCsvImport";
|
||||||
import { isHanmacFamilyTenant, isHanmacFamilyUser } from "../orgChartPicker";
|
import { isHanmacFamilyTenant, isHanmacFamilyUser } from "../orgChartPicker";
|
||||||
import { parseUserCSV } from "../utils/csvParser";
|
import { parseUserCSV } from "../utils/csvParser";
|
||||||
import {
|
|
||||||
type HanmacImportEmailPreview,
|
|
||||||
buildHanmacImportEmailPreview,
|
|
||||||
} from "../utils/hanmacImportEmail";
|
|
||||||
import { applyGeneralPlanningOfficePriority } from "../utils/generalPlanningOfficePriority";
|
import { applyGeneralPlanningOfficePriority } from "../utils/generalPlanningOfficePriority";
|
||||||
|
import {
|
||||||
|
buildHanmacImportEmailPreview,
|
||||||
|
type HanmacImportEmailPreview,
|
||||||
|
} from "../utils/hanmacImportEmail";
|
||||||
|
|
||||||
interface UserBulkUploadModalProps {
|
interface UserBulkUploadModalProps {
|
||||||
onSuccess?: () => void;
|
onSuccess?: () => void;
|
||||||
@@ -551,7 +551,10 @@ export function UserBulkUploadModal({
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{previewData.slice(0, 10).map((u, index) => (
|
{previewData.slice(0, 10).map((u, index) => (
|
||||||
<tr key={`${u.email}-${index}`} className="border-t">
|
<tr
|
||||||
|
key={`${u.email}-${u.tenantSlug ?? ""}-${u.name}`}
|
||||||
|
className="border-t"
|
||||||
|
>
|
||||||
<td className="p-2">
|
<td className="p-2">
|
||||||
<input
|
<input
|
||||||
className="h-8 w-full min-w-[180px] rounded-md border border-input bg-background px-2 font-mono text-xs"
|
className="h-8 w-full min-w-[180px] rounded-md border border-input bg-background px-2 font-mono text-xs"
|
||||||
|
|||||||
@@ -59,9 +59,7 @@ describe("orgChartPicker", () => {
|
|||||||
buildAuthenticatedOrgChartUrl("https://orgchart.example.com/", {
|
buildAuthenticatedOrgChartUrl("https://orgchart.example.com/", {
|
||||||
includeInternal: false,
|
includeInternal: false,
|
||||||
}),
|
}),
|
||||||
).toBe(
|
).toBe("https://orgchart.example.com/login?auto=1&returnTo=%2Fchart");
|
||||||
"https://orgchart.example.com/login?auto=1&returnTo=%2Fchart",
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("parses the first tenant id and name from orgfront confirm messages", () => {
|
it("parses the first tenant id and name from orgfront confirm messages", () => {
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ export const userStatusValues = [
|
|||||||
|
|
||||||
export type UserStatusValue = (typeof userStatusValues)[number];
|
export type UserStatusValue = (typeof userStatusValues)[number];
|
||||||
|
|
||||||
export function normalizeUserStatusValue(status?: string | null): UserStatusValue {
|
export function normalizeUserStatusValue(
|
||||||
|
status?: string | null,
|
||||||
|
): UserStatusValue {
|
||||||
switch ((status ?? "").trim().toLowerCase()) {
|
switch ((status ?? "").trim().toLowerCase()) {
|
||||||
case "active":
|
case "active":
|
||||||
return "active";
|
return "active";
|
||||||
|
|||||||
@@ -238,9 +238,7 @@ function normalizeHeader(header: string) {
|
|||||||
"worksmobile_alias_email",
|
"worksmobile_alias_email",
|
||||||
"worksmobile_alias_emails",
|
"worksmobile_alias_emails",
|
||||||
].includes(separatorNormalized) ||
|
].includes(separatorNormalized) ||
|
||||||
["보조이메일", "보조메일", "추가이메일", "추가메일"].includes(
|
["보조이메일", "보조메일", "추가이메일", "추가메일"].includes(compactKorean)
|
||||||
compactKorean,
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
return "secondary_emails";
|
return "secondary_emails";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,8 @@
|
|||||||
--input: 215 25% 24%;
|
--input: 215 25% 24%;
|
||||||
--ring: 209 79% 52%;
|
--ring: 209 79% 52%;
|
||||||
--radius: 0.75rem;
|
--radius: 0.75rem;
|
||||||
--app-background-image: radial-gradient(
|
--app-background-image:
|
||||||
|
radial-gradient(
|
||||||
circle at 10% 18%,
|
circle at 10% 18%,
|
||||||
rgba(54, 211, 153, 0.16),
|
rgba(54, 211, 153, 0.16),
|
||||||
transparent 28%
|
transparent 28%
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { shouldStartLoginRedirect } from "../../../common/core/auth";
|
import { shouldStartLoginRedirect } from "../../../common/core/auth";
|
||||||
import {
|
import { shouldSuppressDevelopmentSessionRedirect } from "../../../common/core/session";
|
||||||
shouldSuppressDevelopmentSessionRedirect,
|
|
||||||
} from "../../../common/core/session";
|
|
||||||
import { userManager } from "./auth";
|
import { userManager } from "./auth";
|
||||||
|
|
||||||
let isRedirectingToLogin = false;
|
let isRedirectingToLogin = false;
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
const CLIENT_DEBUG_LOG_ENABLED = new Set(["1", "true", "yes", "y", "on"]).has(
|
const CLIENT_DEBUG_LOG_ENABLED = new Set(["1", "true", "yes", "y", "on"]).has(
|
||||||
String(import.meta.env.VITE_CLIENT_LOG_DEBUG ?? "").trim().toLowerCase(),
|
String(import.meta.env.VITE_CLIENT_LOG_DEBUG ?? "")
|
||||||
|
.trim()
|
||||||
|
.toLowerCase(),
|
||||||
);
|
);
|
||||||
|
|
||||||
export function debugLog(...args: Parameters<typeof console.debug>) {
|
export function debugLog(...args: Parameters<typeof console.debug>) {
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import { DEFAULT_LOCALE, LOCALE_STORAGE_KEY, type Locale } from "../../../common/core/i18n";
|
import {
|
||||||
|
DEFAULT_LOCALE,
|
||||||
|
LOCALE_STORAGE_KEY,
|
||||||
|
type Locale,
|
||||||
|
} from "../../../common/core/i18n";
|
||||||
|
|
||||||
function isLocale(value: string): value is Locale {
|
function isLocale(value: string): value is Locale {
|
||||||
return value === "ko" || value === "en";
|
return value === "ko" || value === "en";
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import {
|
import {
|
||||||
|
isSuperAdminRole,
|
||||||
|
normalizeAdminRole,
|
||||||
ROLE_RP_ADMIN,
|
ROLE_RP_ADMIN,
|
||||||
ROLE_SUPER_ADMIN,
|
ROLE_SUPER_ADMIN,
|
||||||
ROLE_TENANT_ADMIN,
|
ROLE_TENANT_ADMIN,
|
||||||
ROLE_USER,
|
ROLE_USER,
|
||||||
isSuperAdminRole,
|
|
||||||
normalizeAdminRole,
|
|
||||||
} from "./roles";
|
} from "./roles";
|
||||||
|
|
||||||
describe("admin role helpers", () => {
|
describe("admin role helpers", () => {
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import {
|
|||||||
readSessionExpiryEnabled,
|
readSessionExpiryEnabled,
|
||||||
SESSION_RENEW_THRESHOLD_MS,
|
SESSION_RENEW_THRESHOLD_MS,
|
||||||
shouldAttemptSlidingSessionRenew,
|
shouldAttemptSlidingSessionRenew,
|
||||||
shouldSuppressDevelopmentSessionRedirect,
|
|
||||||
shouldAttemptUnlimitedSessionRenew,
|
shouldAttemptUnlimitedSessionRenew,
|
||||||
|
shouldSuppressDevelopmentSessionRedirect,
|
||||||
writeSessionExpiryEnabled,
|
writeSessionExpiryEnabled,
|
||||||
} from "./sessionSliding";
|
} from "./sessionSliding";
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
export {
|
export {
|
||||||
DEFAULT_SESSION_RENEW_THROTTLE_MS as SESSION_RENEW_THROTTLE_MS,
|
|
||||||
DEFAULT_SESSION_RENEW_THRESHOLD_MS as SESSION_RENEW_THRESHOLD_MS,
|
DEFAULT_SESSION_RENEW_THRESHOLD_MS as SESSION_RENEW_THRESHOLD_MS,
|
||||||
|
DEFAULT_SESSION_RENEW_THROTTLE_MS as SESSION_RENEW_THROTTLE_MS,
|
||||||
readSessionExpiryEnabled,
|
readSessionExpiryEnabled,
|
||||||
shouldAttemptSlidingSessionRenew,
|
shouldAttemptSlidingSessionRenew,
|
||||||
shouldSuppressDevelopmentSessionRedirect,
|
|
||||||
shouldAttemptUnlimitedSessionRenew,
|
shouldAttemptUnlimitedSessionRenew,
|
||||||
|
shouldSuppressDevelopmentSessionRedirect,
|
||||||
writeSessionExpiryEnabled,
|
writeSessionExpiryEnabled,
|
||||||
} from "../../../common/core/session";
|
} from "../../../common/core/session";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import {
|
import {
|
||||||
type SortConfig,
|
|
||||||
compareNullableValues,
|
compareNullableValues,
|
||||||
|
type SortConfig,
|
||||||
sortItems,
|
sortItems,
|
||||||
toggleSort,
|
toggleSort,
|
||||||
} from "../../../common/core/utils";
|
} from "../../../common/core/utils";
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import { StrictMode } from "react";
|
|||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
import { AuthProvider } from "react-oidc-context";
|
import { AuthProvider } from "react-oidc-context";
|
||||||
import { RouterProvider } from "react-router-dom";
|
import { RouterProvider } from "react-router-dom";
|
||||||
import LocaleRefreshBoundary from "./components/common/LocaleRefreshBoundary";
|
|
||||||
import { queryClient } from "./app/queryClient";
|
import { queryClient } from "./app/queryClient";
|
||||||
import { router } from "./app/routes";
|
import { router } from "./app/routes";
|
||||||
|
import LocaleRefreshBoundary from "./components/common/LocaleRefreshBoundary";
|
||||||
import { Toaster } from "./components/ui/toaster";
|
import { Toaster } from "./components/ui/toaster";
|
||||||
import { oidcConfig } from "./lib/auth";
|
import { oidcConfig } from "./lib/auth";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|||||||
@@ -74,8 +74,7 @@ const translations: Record<"ko" | "en", Record<string, string>> = {
|
|||||||
"user_login_ids.user_id가 존재하지 않거나 soft-deleted user를 참조하는지 검사합니다.",
|
"user_login_ids.user_id가 존재하지 않거나 soft-deleted user를 참조하는지 검사합니다.",
|
||||||
"msg.admin.integrity.check.orphan_user_tenant_memberships.description":
|
"msg.admin.integrity.check.orphan_user_tenant_memberships.description":
|
||||||
"users.tenant_id가 존재하지 않거나 soft-deleted tenant를 참조하는지 검사합니다.",
|
"users.tenant_id가 존재하지 않거나 soft-deleted tenant를 참조하는지 검사합니다.",
|
||||||
"msg.admin.integrity.recheck.running":
|
"msg.admin.integrity.recheck.running": "정합성 검사를 실행 중입니다.",
|
||||||
"정합성 검사를 실행 중입니다.",
|
|
||||||
"msg.admin.integrity.recheck.success": "검사가 완료되었습니다.",
|
"msg.admin.integrity.recheck.success": "검사가 완료되었습니다.",
|
||||||
"msg.admin.user_projection.forbidden.description":
|
"msg.admin.user_projection.forbidden.description":
|
||||||
"이 화면은 super_admin 권한으로만 접근할 수 있습니다.",
|
"이 화면은 super_admin 권한으로만 접근할 수 있습니다.",
|
||||||
@@ -103,7 +102,8 @@ const translations: Record<"ko" | "en", Record<string, string>> = {
|
|||||||
"ui.admin.auth_guard.checker.denied": "Access DENIED",
|
"ui.admin.auth_guard.checker.denied": "Access DENIED",
|
||||||
"ui.admin.auth_guard.checker.denied_description":
|
"ui.admin.auth_guard.checker.denied_description":
|
||||||
"The subject does not have access to the requested resource.",
|
"The subject does not have access to the requested resource.",
|
||||||
"ui.admin.integrity.check.duplicate_tenant_slugs.title": "Duplicate tenant slug",
|
"ui.admin.integrity.check.duplicate_tenant_slugs.title":
|
||||||
|
"Duplicate tenant slug",
|
||||||
"ui.admin.integrity.section.tenant_integrity": "Tenant integrity",
|
"ui.admin.integrity.section.tenant_integrity": "Tenant integrity",
|
||||||
"ui.admin.integrity.section.user_integrity": "User integrity",
|
"ui.admin.integrity.section.user_integrity": "User integrity",
|
||||||
"ui.admin.integrity.title": "Data Integrity Check",
|
"ui.admin.integrity.title": "Data Integrity Check",
|
||||||
@@ -173,7 +173,8 @@ function format(template: string, vars?: Vars) {
|
|||||||
export function createI18nMock() {
|
export function createI18nMock() {
|
||||||
return {
|
return {
|
||||||
t(key: string, fallback?: string, vars?: Vars) {
|
t(key: string, fallback?: string, vars?: Vars) {
|
||||||
const locale = window.localStorage.getItem("locale") === "en" ? "en" : "ko";
|
const locale =
|
||||||
|
window.localStorage.getItem("locale") === "en" ? "en" : "ko";
|
||||||
const template = translations[locale][key] ?? fallback ?? key;
|
const template = translations[locale][key] ?? fallback ?? key;
|
||||||
return format(template, vars);
|
return format(template, vars);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -164,7 +164,9 @@ test.describe("Tenants Management", () => {
|
|||||||
await expect(page.locator("table")).toContainText("Platform");
|
await expect(page.locator("table")).toContainText("Platform");
|
||||||
await expect(page.locator("table")).toContainText("Acme");
|
await expect(page.locator("table")).toContainText("Acme");
|
||||||
|
|
||||||
await page.getByPlaceholder(/테넌트 이름 또는 슬러그 검색|search/i).fill("");
|
await page
|
||||||
|
.getByPlaceholder(/테넌트 이름 또는 슬러그 검색|search/i)
|
||||||
|
.fill("");
|
||||||
await page
|
await page
|
||||||
.locator("tbody tr")
|
.locator("tbody tr")
|
||||||
.filter({ hasText: "Planning" })
|
.filter({ hasText: "Planning" })
|
||||||
@@ -538,7 +540,10 @@ test.describe("Tenants Management", () => {
|
|||||||
test("should create a hanmac-family child tenant with org config", async ({
|
test("should create a hanmac-family child tenant with org config", async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
test.skip(true, "브라우저별 org picker 상호작용이 불안정하여 unit 테스트로 커버합니다.");
|
test.skip(
|
||||||
|
true,
|
||||||
|
"브라우저별 org picker 상호작용이 불안정하여 unit 테스트로 커버합니다.",
|
||||||
|
);
|
||||||
await page.setViewportSize({ width: 1280, height: 800 });
|
await page.setViewportSize({ width: 1280, height: 800 });
|
||||||
let createBody = "";
|
let createBody = "";
|
||||||
const tenants = [
|
const tenants = [
|
||||||
|
|||||||
@@ -470,6 +470,193 @@ test.describe("User Management", () => {
|
|||||||
.toMatchObject({ status: "preboarding" });
|
.toMatchObject({ status: "preboarding" });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("should center users table loading state and use compact headers", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
let resolveUsers: (() => void) | undefined;
|
||||||
|
const usersGate = new Promise<void>((resolve) => {
|
||||||
|
resolveUsers = resolve;
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.route(/\/admin\/users(\?.*)?$/, async (route) => {
|
||||||
|
if (route.request().method() !== "GET") {
|
||||||
|
return route.fallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
await usersGate;
|
||||||
|
return route.fulfill({
|
||||||
|
json: {
|
||||||
|
items: [],
|
||||||
|
total: 0,
|
||||||
|
limit: 50,
|
||||||
|
offset: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.goto("/users");
|
||||||
|
|
||||||
|
const loadingCell = page.getByTestId("user-table-loading-cell");
|
||||||
|
await expect(loadingCell).toBeVisible();
|
||||||
|
await expect(loadingCell).toHaveCSS("display", "flex");
|
||||||
|
await expect(loadingCell).toHaveCSS("align-items", "center");
|
||||||
|
await expect(loadingCell).toHaveCSS("justify-content", "center");
|
||||||
|
|
||||||
|
const nameHeader = page.getByRole("columnheader", { name: /이름|Name/i });
|
||||||
|
await expect(nameHeader).toHaveClass(/h-9/);
|
||||||
|
await expect(nameHeader.locator("> div")).toHaveClass(/h-full/);
|
||||||
|
|
||||||
|
resolveUsers?.();
|
||||||
|
await expect(page.getByTestId("user-table-empty-cell")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should virtualize large user result rows in the users table", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const manyUsers = Array.from({ length: 500 }, (_, index) => ({
|
||||||
|
id: `u-${index}`,
|
||||||
|
name: `User ${index}`,
|
||||||
|
email: `user${index}@test.com`,
|
||||||
|
phone: "010-1111-2222",
|
||||||
|
loginId: `user${index}`,
|
||||||
|
role: "user",
|
||||||
|
status: "active",
|
||||||
|
createdAt: "2026-04-01T00:00:00Z",
|
||||||
|
}));
|
||||||
|
|
||||||
|
await page.route(/\/admin\/users(\?.*)?$/, async (route) => {
|
||||||
|
if (route.request().method() !== "GET") {
|
||||||
|
return route.fallback();
|
||||||
|
}
|
||||||
|
return route.fulfill({
|
||||||
|
json: {
|
||||||
|
items: manyUsers,
|
||||||
|
total: manyUsers.length,
|
||||||
|
limit: manyUsers.length,
|
||||||
|
offset: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.goto("/users");
|
||||||
|
await expect(page.getByText("User 0")).toBeVisible();
|
||||||
|
|
||||||
|
const renderedStatusControls = await page
|
||||||
|
.getByTestId(/^user-status-select-/)
|
||||||
|
.count();
|
||||||
|
expect(renderedStatusControls).toBeLessThan(manyUsers.length);
|
||||||
|
await expect(page.getByText("User 499")).toHaveCount(0);
|
||||||
|
|
||||||
|
await page.getByTestId("user-table-viewport").evaluate((element) => {
|
||||||
|
element.scrollTop = element.scrollHeight;
|
||||||
|
element.dispatchEvent(new Event("scroll", { bubbles: true }));
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(page.getByText("User 499")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should keep large user search rendering under 200ms", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const manyUsers = Array.from({ length: 20_000 }, (_, index) => ({
|
||||||
|
id: `load-u-${index}`,
|
||||||
|
name: `Load User ${index}`,
|
||||||
|
email: `load-user-${index}@test.com`,
|
||||||
|
phone: "010-1111-2222",
|
||||||
|
loginId: `load-user-${index}`,
|
||||||
|
role: "user",
|
||||||
|
status: "active",
|
||||||
|
createdAt: "2026-04-01T00:00:00Z",
|
||||||
|
}));
|
||||||
|
|
||||||
|
await page.route(/\/admin\/users(\?.*)?$/, async (route) => {
|
||||||
|
if (route.request().method() !== "GET") {
|
||||||
|
return route.fallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = new URL(route.request().url());
|
||||||
|
const normalizedSearch = url.searchParams
|
||||||
|
.get("search")
|
||||||
|
?.trim()
|
||||||
|
.toLowerCase();
|
||||||
|
const items = normalizedSearch
|
||||||
|
? manyUsers.filter((user) =>
|
||||||
|
`${user.name} ${user.email}`
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(normalizedSearch),
|
||||||
|
)
|
||||||
|
: manyUsers;
|
||||||
|
|
||||||
|
return route.fulfill({
|
||||||
|
json: {
|
||||||
|
items,
|
||||||
|
total: items.length,
|
||||||
|
limit: items.length,
|
||||||
|
offset: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const initialStartedAt = performance.now();
|
||||||
|
await page.goto("/users");
|
||||||
|
await expect(page.getByText("Load User 0")).toBeVisible();
|
||||||
|
const initialMs = performance.now() - initialStartedAt;
|
||||||
|
|
||||||
|
const searchInput = page.getByPlaceholder("이름 또는 이메일 검색...");
|
||||||
|
await searchInput.fill("Load User 19999");
|
||||||
|
const searchMs = await page.evaluate(async () => {
|
||||||
|
const input = Array.from(document.querySelectorAll("input")).find(
|
||||||
|
(candidate) => candidate.placeholder === "이름 또는 이메일 검색...",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!input) {
|
||||||
|
throw new Error("User search input was not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return await new Promise<number>((resolve, reject) => {
|
||||||
|
const startedAt = performance.now();
|
||||||
|
const timeout = window.setTimeout(() => {
|
||||||
|
observer.disconnect();
|
||||||
|
reject(new Error("Timed out waiting for large user search result."));
|
||||||
|
}, 1000);
|
||||||
|
const observer = new MutationObserver(() => {
|
||||||
|
const bodyText = document.body.textContent ?? "";
|
||||||
|
if (
|
||||||
|
bodyText.includes("Load User 19999") &&
|
||||||
|
!bodyText.includes("Load User 0")
|
||||||
|
) {
|
||||||
|
window.clearTimeout(timeout);
|
||||||
|
observer.disconnect();
|
||||||
|
resolve(performance.now() - startedAt);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(document.body, {
|
||||||
|
childList: true,
|
||||||
|
characterData: true,
|
||||||
|
subtree: true,
|
||||||
|
});
|
||||||
|
input.dispatchEvent(
|
||||||
|
new KeyboardEvent("keydown", {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
key: "Enter",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
await expect(page.getByText("Load User 19999")).toBeVisible();
|
||||||
|
await expect(page.getByText("Load User 0")).toHaveCount(0);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`[perf] users initial render with ${manyUsers.length} rows: ${initialMs.toFixed(1)}ms`,
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`[perf] users search update with ${manyUsers.length} rows: ${searchMs.toFixed(1)}ms`,
|
||||||
|
);
|
||||||
|
expect(searchMs).toBeLessThan(200);
|
||||||
|
});
|
||||||
|
|
||||||
test("should expose internal user uuid in the users table", async ({
|
test("should expose internal user uuid in the users table", async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { defineConfig } from "vite";
|
|
||||||
import react from "@vitejs/plugin-react";
|
import react from "@vitejs/plugin-react";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
import { defineConfig } from "vite";
|
||||||
|
|
||||||
const buildOutDir = process.env.ADMINFRONT_BUILD_OUT_DIR ?? "dist";
|
const buildOutDir = process.env.ADMINFRONT_BUILD_OUT_DIR ?? "dist";
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
|
import { fileURLToPath } from "node:url";
|
||||||
import react from "@vitejs/plugin-react";
|
import react from "@vitejs/plugin-react";
|
||||||
import { defineConfig } from "vitest/config";
|
import { defineConfig } from "vitest/config";
|
||||||
|
|
||||||
|
const commonRoot = fileURLToPath(new URL("../common", import.meta.url)).replace(
|
||||||
|
/\\/g,
|
||||||
|
"/",
|
||||||
|
);
|
||||||
|
const commonCoverageIncludes = ["core", "shell", "theme", "ui"].map(
|
||||||
|
(directory) => `${commonRoot}/${directory}/**/*.{ts,tsx}`,
|
||||||
|
);
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
esbuild: {
|
esbuild: {
|
||||||
@@ -11,6 +20,29 @@ export default defineConfig({
|
|||||||
environment: "jsdom",
|
environment: "jsdom",
|
||||||
setupFiles: "./src/test/setup.ts",
|
setupFiles: "./src/test/setup.ts",
|
||||||
include: ["src/**/*.{test,spec}.{ts,tsx}"],
|
include: ["src/**/*.{test,spec}.{ts,tsx}"],
|
||||||
|
coverage: {
|
||||||
|
provider: "v8",
|
||||||
|
reporter: ["text", "html", "lcov", "json-summary"],
|
||||||
|
reportsDirectory: "coverage",
|
||||||
|
all: true,
|
||||||
|
allowExternal: true,
|
||||||
|
include: ["src/**/*.{ts,tsx}", ...commonCoverageIncludes],
|
||||||
|
exclude: [
|
||||||
|
"**/*.{test,spec}.{ts,tsx}",
|
||||||
|
"**/*.d.ts",
|
||||||
|
"**/node_modules/**",
|
||||||
|
"**/dist/**",
|
||||||
|
"**/coverage/**",
|
||||||
|
"src/test/**",
|
||||||
|
"src/main.tsx",
|
||||||
|
"src/vite-env.d.ts",
|
||||||
|
"../common/**/node_modules/**",
|
||||||
|
"../common/.pnpm-store/**",
|
||||||
|
`${commonRoot}/theme/**`,
|
||||||
|
`${commonRoot}/core/pagination/*.worker.ts`,
|
||||||
|
`${commonRoot}/core/query/queryClient.ts`,
|
||||||
|
],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
fs: {
|
fs: {
|
||||||
|
|||||||
4
common/biome.json
Normal file
4
common/biome.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"extends": ["./config/biome.base.json"]
|
||||||
|
}
|
||||||
@@ -1,32 +1,42 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
"root": false,
|
||||||
|
"$schema": "https://biomejs.dev/schemas/2.4.16/schema.json",
|
||||||
"formatter": {
|
"formatter": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"indentStyle": "space"
|
"indentStyle": "space"
|
||||||
},
|
},
|
||||||
|
"css": {
|
||||||
|
"parser": {
|
||||||
|
"tailwindDirectives": true
|
||||||
|
}
|
||||||
|
},
|
||||||
"linter": {
|
"linter": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"rules": {
|
"rules": {
|
||||||
"style": {
|
"style": {
|
||||||
|
"useNodejsImportProtocol": "off",
|
||||||
"useEnumInitializers": "off"
|
"useEnumInitializers": "off"
|
||||||
},
|
},
|
||||||
|
"suspicious": {
|
||||||
|
"noUnknownAtRules": "off"
|
||||||
|
},
|
||||||
"a11y": {
|
"a11y": {
|
||||||
"noLabelWithoutControl": "off"
|
"noLabelWithoutControl": "off"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"organizeImports": {
|
"assist": { "actions": { "source": { "organizeImports": "on" } } },
|
||||||
"enabled": true
|
|
||||||
},
|
|
||||||
"files": {
|
"files": {
|
||||||
"ignore": [
|
"includes": [
|
||||||
"dist",
|
"**",
|
||||||
".vite",
|
"!**/dist/**",
|
||||||
"node_modules",
|
"!**/.vite/**",
|
||||||
"tsconfig*.json",
|
"!**/node_modules/**",
|
||||||
"test-results",
|
"!**/coverage/**",
|
||||||
"test-results.nobody-backup",
|
"!**/tsconfig*.json",
|
||||||
"playwright-report"
|
"!**/test-results/**",
|
||||||
|
"!**/test-results.nobody-backup/**",
|
||||||
|
"!**/playwright-report/**"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { createRequire } from "node:module";
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import react from "@vitejs/plugin-react";
|
import react from "@vitejs/plugin-react";
|
||||||
import { type UserConfig, defineConfig } from "vite";
|
import { defineConfig, type UserConfig } from "vite";
|
||||||
|
|
||||||
const require = createRequire(import.meta.url);
|
const require = createRequire(import.meta.url);
|
||||||
const commonWorkspaceDir = path.resolve(
|
const commonWorkspaceDir = path.resolve(
|
||||||
|
|||||||
@@ -1,17 +1,8 @@
|
|||||||
import { ChevronDown, ChevronUp, Copy } from "lucide-react";
|
import { ChevronDown, ChevronUp, Copy } from "lucide-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import type { CommonAuditLog } from "../../audit";
|
|
||||||
import {
|
import {
|
||||||
formatAuditDateParts,
|
|
||||||
formatAuditValue,
|
|
||||||
parseAuditDetails,
|
|
||||||
resolveAuditAction,
|
|
||||||
resolveAuditActor,
|
|
||||||
resolveAuditTarget,
|
|
||||||
} from "../../audit";
|
|
||||||
import {
|
|
||||||
getCommonBadgeClasses,
|
|
||||||
type CommonBadgeVariant,
|
type CommonBadgeVariant,
|
||||||
|
getCommonBadgeClasses,
|
||||||
} from "../../../ui/badge";
|
} from "../../../ui/badge";
|
||||||
import { getCommonButtonClasses } from "../../../ui/button";
|
import { getCommonButtonClasses } from "../../../ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -26,6 +17,15 @@ import {
|
|||||||
commonTableViewportClass,
|
commonTableViewportClass,
|
||||||
commonTableWrapperClass,
|
commonTableWrapperClass,
|
||||||
} from "../../../ui/table";
|
} from "../../../ui/table";
|
||||||
|
import type { CommonAuditLog } from "../../audit";
|
||||||
|
import {
|
||||||
|
formatAuditDateParts,
|
||||||
|
formatAuditValue,
|
||||||
|
parseAuditDetails,
|
||||||
|
resolveAuditAction,
|
||||||
|
resolveAuditActor,
|
||||||
|
resolveAuditTarget,
|
||||||
|
} from "../../audit";
|
||||||
|
|
||||||
type AuditTranslate = (
|
type AuditTranslate = (
|
||||||
key: string,
|
key: string,
|
||||||
@@ -77,7 +77,10 @@ export function AuditLogTable({
|
|||||||
<div className={commonTableWrapperClass}>
|
<div className={commonTableWrapperClass}>
|
||||||
<table className={cx(commonTableClass, "table-fixed")}>
|
<table className={cx(commonTableClass, "table-fixed")}>
|
||||||
<thead
|
<thead
|
||||||
className={cx(commonTableHeaderClass, commonStickyTableHeaderClass)}
|
className={cx(
|
||||||
|
commonTableHeaderClass,
|
||||||
|
commonStickyTableHeaderClass,
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<tr className={commonTableRowClass}>
|
<tr className={commonTableRowClass}>
|
||||||
<th className={cx(commonTableHeadClass, "w-[190px]")}>
|
<th className={cx(commonTableHeadClass, "w-[190px]")}>
|
||||||
@@ -115,7 +118,10 @@ export function AuditLogTable({
|
|||||||
<tr className={commonTableRowClass}>
|
<tr className={commonTableRowClass}>
|
||||||
<td
|
<td
|
||||||
colSpan={6}
|
colSpan={6}
|
||||||
className={cx(commonTableCellClass, "text-center text-muted-foreground")}
|
className={cx(
|
||||||
|
commonTableCellClass,
|
||||||
|
"text-center text-muted-foreground",
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
{t("msg.common.audit.empty", "No audit logs found.")}
|
{t("msg.common.audit.empty", "No audit logs found.")}
|
||||||
</td>
|
</td>
|
||||||
@@ -132,7 +138,12 @@ export function AuditLogTable({
|
|||||||
return (
|
return (
|
||||||
<React.Fragment key={rowKey}>
|
<React.Fragment key={rowKey}>
|
||||||
<tr className={cx(commonTableRowClass, "bg-card/40")}>
|
<tr className={cx(commonTableRowClass, "bg-card/40")}>
|
||||||
<td className={cx(commonTableCellClass, "text-xs text-muted-foreground")}>
|
<td
|
||||||
|
className={cx(
|
||||||
|
commonTableCellClass,
|
||||||
|
"text-xs text-muted-foreground",
|
||||||
|
)}
|
||||||
|
>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div>{date}</div>
|
<div>{date}</div>
|
||||||
<div>{time}</div>
|
<div>{time}</div>
|
||||||
@@ -165,14 +176,20 @@ export function AuditLogTable({
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td
|
<td
|
||||||
className={cx(commonTableCellClass, "text-xs text-muted-foreground")}
|
className={cx(
|
||||||
|
commonTableCellClass,
|
||||||
|
"text-xs text-muted-foreground",
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<div className="font-semibold text-foreground">
|
<div className="font-semibold text-foreground">
|
||||||
{actionLabel}
|
{actionLabel}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td
|
<td
|
||||||
className={cx(commonTableCellClass, "text-xs text-muted-foreground")}
|
className={cx(
|
||||||
|
commonTableCellClass,
|
||||||
|
"text-xs text-muted-foreground",
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="break-all">{targetLabel}</span>
|
<span className="break-all">{targetLabel}</span>
|
||||||
@@ -230,18 +247,26 @@ export function AuditLogTable({
|
|||||||
</tr>
|
</tr>
|
||||||
{expanded ? (
|
{expanded ? (
|
||||||
<tr className={cx(commonTableRowClass, "bg-card/20")}>
|
<tr className={cx(commonTableRowClass, "bg-card/20")}>
|
||||||
<td colSpan={6} className={cx(commonTableCellClass, "text-xs")}>
|
<td
|
||||||
|
colSpan={6}
|
||||||
|
className={cx(commonTableCellClass, "text-xs")}
|
||||||
|
>
|
||||||
<div className="grid gap-4 text-muted-foreground md:grid-cols-3">
|
<div className="grid gap-4 text-muted-foreground md:grid-cols-3">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="uppercase tracking-[0.16em]">
|
<div className="uppercase tracking-[0.16em]">
|
||||||
{t("ui.common.audit.details.request", "Request")}
|
{t(
|
||||||
|
"ui.common.audit.details.request",
|
||||||
|
"Request",
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="break-all">
|
<div className="break-all">
|
||||||
{t(
|
{t(
|
||||||
"ui.common.audit.details.request_id",
|
"ui.common.audit.details.request_id",
|
||||||
"Request ID · {{value}}",
|
"Request ID · {{value}}",
|
||||||
{
|
{
|
||||||
value: formatAuditValue(details.request_id),
|
value: formatAuditValue(
|
||||||
|
details.request_id,
|
||||||
|
),
|
||||||
},
|
},
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -255,9 +280,13 @@ export function AuditLogTable({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{t("ui.common.audit.details.ip", "IP · {{value}}", {
|
{t(
|
||||||
value: formatAuditValue(row.ip_address),
|
"ui.common.audit.details.ip",
|
||||||
})}
|
"IP · {{value}}",
|
||||||
|
{
|
||||||
|
value: formatAuditValue(row.ip_address),
|
||||||
|
},
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="break-all">
|
<div className="break-all">
|
||||||
{t(
|
{t(
|
||||||
@@ -306,7 +335,9 @@ export function AuditLogTable({
|
|||||||
"ui.common.audit.details.tenant",
|
"ui.common.audit.details.tenant",
|
||||||
"Tenant · {{value}}",
|
"Tenant · {{value}}",
|
||||||
{
|
{
|
||||||
value: formatAuditValue(details.tenant_id),
|
value: formatAuditValue(
|
||||||
|
details.tenant_id,
|
||||||
|
),
|
||||||
},
|
},
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -329,7 +360,10 @@ export function AuditLogTable({
|
|||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="uppercase tracking-[0.16em]">
|
<div className="uppercase tracking-[0.16em]">
|
||||||
{t("ui.common.audit.details.result", "Result")}
|
{t(
|
||||||
|
"ui.common.audit.details.result",
|
||||||
|
"Result",
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="break-all">
|
<div className="break-all">
|
||||||
{t(
|
{t(
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
export { OverviewMetric } from "./OverviewMetric";
|
|
||||||
export { OverviewAxisNotes } from "./OverviewAxisNotes";
|
export { OverviewAxisNotes } from "./OverviewAxisNotes";
|
||||||
|
export { OverviewMetric } from "./OverviewMetric";
|
||||||
export { OverviewSelectionChips } from "./OverviewSelectionChips";
|
export { OverviewSelectionChips } from "./OverviewSelectionChips";
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export function PageHeader({
|
|||||||
className={cx(
|
className={cx(
|
||||||
"flex flex-wrap items-start justify-between gap-4",
|
"flex flex-wrap items-start justify-between gap-4",
|
||||||
sticky &&
|
sticky &&
|
||||||
"sticky top-[-2.5rem] z-20 -mt-4 bg-background/95 pt-4 pb-2 backdrop-blur",
|
"sticky top-[-2.5rem] z-20 -mt-4 bg-background/95 pt-4 pb-2 backdrop-blur",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
import type { ReactNode, ThHTMLAttributes } from "react";
|
import type { ReactNode, ThHTMLAttributes } from "react";
|
||||||
import type { SortConfig } from "../../utils";
|
|
||||||
import {
|
import {
|
||||||
commonStickyTableHeaderClass,
|
commonStickyTableHeaderClass,
|
||||||
commonTableHeadClass,
|
commonTableHeadClass,
|
||||||
} from "../../../ui/table";
|
} from "../../../ui/table";
|
||||||
|
import type { SortConfig } from "../../utils";
|
||||||
|
|
||||||
export const sortableTableHeadBaseClassName =
|
export const sortableTableHeadBaseClassName = commonTableHeadClass;
|
||||||
commonTableHeadClass;
|
|
||||||
|
|
||||||
export const sortableTableHeaderClassName =
|
export const sortableTableHeaderClassName = commonStickyTableHeaderClass;
|
||||||
commonStickyTableHeaderClass;
|
|
||||||
|
|
||||||
function SortAscendingIcon() {
|
function SortAscendingIcon() {
|
||||||
return (
|
return (
|
||||||
@@ -126,7 +124,7 @@ export function SortableTableHead<Key extends string>({
|
|||||||
...props
|
...props
|
||||||
}: SortableTableHeadProps<Key>) {
|
}: SortableTableHeadProps<Key>) {
|
||||||
const isActive = sortConfig?.key === sortKey;
|
const isActive = sortConfig?.key === sortKey;
|
||||||
const direction = isActive ? sortConfig?.direction ?? null : null;
|
const direction = isActive ? (sortConfig?.direction ?? null) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<th
|
<th
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ export { createTomlTranslator } from "./loader";
|
|||||||
export {
|
export {
|
||||||
DEFAULT_LOCALE,
|
DEFAULT_LOCALE,
|
||||||
LOCALE_STORAGE_KEY,
|
LOCALE_STORAGE_KEY,
|
||||||
SUPPORTED_LOCALES,
|
|
||||||
type Locale,
|
type Locale,
|
||||||
|
SUPPORTED_LOCALES,
|
||||||
type TomlObject,
|
type TomlObject,
|
||||||
type TomlValue,
|
type TomlValue,
|
||||||
type TranslatorInput,
|
type TranslatorInput,
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
DEFAULT_LOCALE,
|
DEFAULT_LOCALE,
|
||||||
LOCALE_STORAGE_KEY,
|
LOCALE_STORAGE_KEY,
|
||||||
SUPPORTED_LOCALES,
|
|
||||||
type Locale,
|
type Locale,
|
||||||
|
SUPPORTED_LOCALES,
|
||||||
type TomlObject,
|
type TomlObject,
|
||||||
type TomlValue,
|
type TomlValue,
|
||||||
type TranslatorInput,
|
type TranslatorInput,
|
||||||
@@ -155,10 +155,16 @@ export function createTomlTranslator(
|
|||||||
const translations: Record<Locale, TomlObject> = {
|
const translations: Record<Locale, TomlObject> = {
|
||||||
ko: input.ko
|
ko: input.ko
|
||||||
.map((raw) => parseToml(raw))
|
.map((raw) => parseToml(raw))
|
||||||
.reduce<TomlObject>((merged, current) => mergeTomlObjects(merged, current), {}),
|
.reduce<TomlObject>(
|
||||||
|
(merged, current) => mergeTomlObjects(merged, current),
|
||||||
|
{},
|
||||||
|
),
|
||||||
en: input.en
|
en: input.en
|
||||||
.map((raw) => parseToml(raw))
|
.map((raw) => parseToml(raw))
|
||||||
.reduce<TomlObject>((merged, current) => mergeTomlObjects(merged, current), {}),
|
.reduce<TomlObject>(
|
||||||
|
(merged, current) => mergeTomlObjects(merged, current),
|
||||||
|
{},
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
return function t(
|
return function t(
|
||||||
|
|||||||
@@ -34,9 +34,12 @@ function shouldUseWorker(useWorker: boolean | undefined) {
|
|||||||
async function fetchAllCursorPagesInWorker<TItem>(
|
async function fetchAllCursorPagesInWorker<TItem>(
|
||||||
request: CursorFetchRequest,
|
request: CursorFetchRequest,
|
||||||
): Promise<CursorPageResponse<TItem>> {
|
): Promise<CursorPageResponse<TItem>> {
|
||||||
const worker = new Worker(new URL("./cursorFetch.worker.ts", import.meta.url), {
|
const worker = new Worker(
|
||||||
type: "module",
|
new URL("./cursorFetch.worker.ts", import.meta.url),
|
||||||
});
|
{
|
||||||
|
type: "module",
|
||||||
|
},
|
||||||
|
);
|
||||||
const id = createRequestId();
|
const id = createRequestId();
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
fetchAllCursorPagesMainThread,
|
|
||||||
type CursorFetchRequest,
|
type CursorFetchRequest,
|
||||||
type CursorPageResponse,
|
type CursorPageResponse,
|
||||||
|
fetchAllCursorPagesMainThread,
|
||||||
} from "./cursorFetchCore";
|
} from "./cursorFetchCore";
|
||||||
|
|
||||||
type CursorWorkerRequestMessage = {
|
type CursorWorkerRequestMessage = {
|
||||||
@@ -21,23 +21,24 @@ type CursorWorkerResponseMessage<TItem> =
|
|||||||
error: string;
|
error: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
self.addEventListener("message", async (event: MessageEvent<CursorWorkerRequestMessage>) => {
|
self.addEventListener(
|
||||||
const { id, request } = event.data;
|
"message",
|
||||||
|
async (event: MessageEvent<CursorWorkerRequestMessage>) => {
|
||||||
|
const { id, request } = event.data;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetchAllCursorPagesMainThread(request);
|
const response = await fetchAllCursorPagesMainThread(request);
|
||||||
self.postMessage({
|
self.postMessage({
|
||||||
id,
|
id,
|
||||||
ok: true,
|
ok: true,
|
||||||
response,
|
response,
|
||||||
} satisfies CursorWorkerResponseMessage<unknown>);
|
} satisfies CursorWorkerResponseMessage<unknown>);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
self.postMessage({
|
self.postMessage({
|
||||||
id,
|
id,
|
||||||
ok: false,
|
ok: false,
|
||||||
error: error instanceof Error ? error.message : String(error),
|
error: error instanceof Error ? error.message : String(error),
|
||||||
} satisfies CursorWorkerResponseMessage<unknown>);
|
} satisfies CursorWorkerResponseMessage<unknown>);
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
);
|
||||||
export {};
|
|
||||||
|
|||||||
@@ -74,7 +74,9 @@ export async function fetchAllCursorPagesMainThread<TItem>({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Cursor page request failed with status ${response.status}`);
|
throw new Error(
|
||||||
|
`Cursor page request failed with status ${response.status}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const page = (await response.json()) as CursorPageResponse<TItem>;
|
const page = (await response.json()) as CursorPageResponse<TItem>;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
export {
|
export {
|
||||||
fetchAllCursorPages,
|
|
||||||
fetchAllCursorPagesMainThread,
|
|
||||||
type CursorFetchRequest,
|
type CursorFetchRequest,
|
||||||
type CursorPageResponse,
|
type CursorPageResponse,
|
||||||
|
fetchAllCursorPages,
|
||||||
|
fetchAllCursorPagesMainThread,
|
||||||
} from "./cursorFetch";
|
} from "./cursorFetch";
|
||||||
|
|||||||
73
common/package-lock.json
generated
73
common/package-lock.json
generated
@@ -29,7 +29,7 @@
|
|||||||
"zod": "^3.24.1"
|
"zod": "^3.24.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "^1.9.4",
|
"@biomejs/biome": "2.4.16",
|
||||||
"@playwright/test": "^1.58.0",
|
"@playwright/test": "^1.58.0",
|
||||||
"@types/node": "^24.10.1",
|
"@types/node": "^24.10.1",
|
||||||
"@types/react": "^19.2.5",
|
"@types/react": "^19.2.5",
|
||||||
@@ -114,11 +114,10 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@biomejs/biome": {
|
"node_modules/@biomejs/biome": {
|
||||||
"version": "1.9.4",
|
"version": "2.4.16",
|
||||||
"resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz",
|
"resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.4.16.tgz",
|
||||||
"integrity": "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==",
|
"integrity": "sha512-x9ajFh1zChVybCiM3TN6OD4phAqLgtPZjFrZF+aTMYCPjwBO+k529TX7PPsAqtGNLeV4UgzwQnowEgS7bGmzcA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
|
||||||
"license": "MIT OR Apache-2.0",
|
"license": "MIT OR Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
"biome": "bin/biome"
|
"biome": "bin/biome"
|
||||||
@@ -131,20 +130,20 @@
|
|||||||
"url": "https://opencollective.com/biome"
|
"url": "https://opencollective.com/biome"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@biomejs/cli-darwin-arm64": "1.9.4",
|
"@biomejs/cli-darwin-arm64": "2.4.16",
|
||||||
"@biomejs/cli-darwin-x64": "1.9.4",
|
"@biomejs/cli-darwin-x64": "2.4.16",
|
||||||
"@biomejs/cli-linux-arm64": "1.9.4",
|
"@biomejs/cli-linux-arm64": "2.4.16",
|
||||||
"@biomejs/cli-linux-arm64-musl": "1.9.4",
|
"@biomejs/cli-linux-arm64-musl": "2.4.16",
|
||||||
"@biomejs/cli-linux-x64": "1.9.4",
|
"@biomejs/cli-linux-x64": "2.4.16",
|
||||||
"@biomejs/cli-linux-x64-musl": "1.9.4",
|
"@biomejs/cli-linux-x64-musl": "2.4.16",
|
||||||
"@biomejs/cli-win32-arm64": "1.9.4",
|
"@biomejs/cli-win32-arm64": "2.4.16",
|
||||||
"@biomejs/cli-win32-x64": "1.9.4"
|
"@biomejs/cli-win32-x64": "2.4.16"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@biomejs/cli-darwin-arm64": {
|
"node_modules/@biomejs/cli-darwin-arm64": {
|
||||||
"version": "1.9.4",
|
"version": "2.4.16",
|
||||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz",
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.4.16.tgz",
|
||||||
"integrity": "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==",
|
"integrity": "sha512-wxPvu4XOA85YJk9ixSWUmq/QBHbid85BISbOAqqBM/5xQpPk9ayjk5375tOlSC0BeCwNSbPFafQBm+vBumXq0A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -159,9 +158,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@biomejs/cli-darwin-x64": {
|
"node_modules/@biomejs/cli-darwin-x64": {
|
||||||
"version": "1.9.4",
|
"version": "2.4.16",
|
||||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz",
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.4.16.tgz",
|
||||||
"integrity": "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==",
|
"integrity": "sha512-xFCqGPwYusQJp4N4NJLi1XJiZqjwFdjhT+KqtNy+Ug3qgfczqnTa6MSDvxJF6TkuDLoYJItMapz6tAf7kCekFw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -176,9 +175,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@biomejs/cli-linux-arm64": {
|
"node_modules/@biomejs/cli-linux-arm64": {
|
||||||
"version": "1.9.4",
|
"version": "2.4.16",
|
||||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz",
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.4.16.tgz",
|
||||||
"integrity": "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==",
|
"integrity": "sha512-2kFb4//jxfZaP6D+Rj5VkHkxgyD9EoRAVBEQb8PKRv+s4NO2zYNJKXFaJmK1CmhufJOWEfpHKaRbOja7qjmdhQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -196,9 +195,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@biomejs/cli-linux-arm64-musl": {
|
"node_modules/@biomejs/cli-linux-arm64-musl": {
|
||||||
"version": "1.9.4",
|
"version": "2.4.16",
|
||||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz",
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.16.tgz",
|
||||||
"integrity": "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==",
|
"integrity": "sha512-oYxnW0ARfJkr72ezzF2OR8N/rtkgLUQeYtF8cFhVswbknHxtTcmzSsanVJP8yQKnGpGpc2ck6c5zLvHahL6Cbg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -216,9 +215,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@biomejs/cli-linux-x64": {
|
"node_modules/@biomejs/cli-linux-x64": {
|
||||||
"version": "1.9.4",
|
"version": "2.4.16",
|
||||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz",
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.4.16.tgz",
|
||||||
"integrity": "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==",
|
"integrity": "sha512-NbcBbi/nJqn5baae6wqRXdS7Gadf2uRpehSh6vMSYpG8OhkXl/Xg8aorWrJ+9VWqAT5ml90alLvorkpMW0nBwQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -236,9 +235,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@biomejs/cli-linux-x64-musl": {
|
"node_modules/@biomejs/cli-linux-x64-musl": {
|
||||||
"version": "1.9.4",
|
"version": "2.4.16",
|
||||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz",
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.16.tgz",
|
||||||
"integrity": "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==",
|
"integrity": "sha512-iHDS+MCM65DPqWGu+ECC3uoALyj2H7F4nVUPxIPjz/PIl94EUu+EDfGZDzFP+NY1EOPVt9NQvwFqq7HdMmowdg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -256,9 +255,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@biomejs/cli-win32-arm64": {
|
"node_modules/@biomejs/cli-win32-arm64": {
|
||||||
"version": "1.9.4",
|
"version": "2.4.16",
|
||||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz",
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.4.16.tgz",
|
||||||
"integrity": "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==",
|
"integrity": "sha512-0rgImMsNb5v/chhkIFe3wu7PEFClS6RBAYUijGL9UsYN3PanSaoK24HSSuSJb1pYbYYVjzAyZTl3gtjJ84BM8A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -273,9 +272,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@biomejs/cli-win32-x64": {
|
"node_modules/@biomejs/cli-win32-x64": {
|
||||||
"version": "1.9.4",
|
"version": "2.4.16",
|
||||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz",
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.4.16.tgz",
|
||||||
"integrity": "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==",
|
"integrity": "sha512-Kp85jgoBHa05gix6UIRjfCDiUV3w/8VIdZ247VyyO2gEjaw12WEVhdIjlxp/AMzXxqxQwbxNTDVZ3Mwd2RG5rw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -4,45 +4,47 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev:all": "pnpm -r run dev",
|
"dev:all": "pnpm -r run dev",
|
||||||
"build:all": "pnpm -r run build",
|
"build:all": "pnpm -r run build",
|
||||||
|
"lint": "biome check .",
|
||||||
|
"lint:fix": "biome check . --write",
|
||||||
"lint:all": "pnpm -r run lint"
|
"lint:all": "pnpm -r run lint"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "^1.9.4",
|
"@biomejs/biome": "2.4.16",
|
||||||
"@playwright/test": "^1.58.0",
|
"@playwright/test": "^1.58.0",
|
||||||
|
"@types/node": "^24.10.1",
|
||||||
|
"@types/react": "^19.2.5",
|
||||||
|
"@types/react-dom": "^19.2.3",
|
||||||
|
"@vitejs/plugin-react": "^6.0.1",
|
||||||
"autoprefixer": "^10.4.23",
|
"autoprefixer": "^10.4.23",
|
||||||
|
"jsdom": "^28.1.0",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"tailwindcss": "^3.4.14",
|
"tailwindcss": "^3.4.14",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~5.9.3",
|
||||||
"vite": "^8.0.3",
|
"vite": "^8.0.3",
|
||||||
"vitest": "^4.1.5",
|
"vitest": "^4.1.5"
|
||||||
"@types/node": "^24.10.1",
|
|
||||||
"@types/react": "^19.2.5",
|
|
||||||
"@types/react-dom": "^19.2.3",
|
|
||||||
"@vitejs/plugin-react": "^6.0.1",
|
|
||||||
"jsdom": "^28.1.0"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "^19.2.0",
|
|
||||||
"react-dom": "^19.2.0",
|
|
||||||
"react-router-dom": "^6.28.2",
|
|
||||||
"@tanstack/react-query": "^5.66.8",
|
|
||||||
"@tanstack/react-query-devtools": "^5.66.8",
|
|
||||||
"axios": "^1.7.9",
|
|
||||||
"lucide-react": "^0.563.0",
|
|
||||||
"clsx": "^2.1.1",
|
|
||||||
"tailwind-merge": "^3.4.0",
|
|
||||||
"class-variance-authority": "^0.7.1",
|
|
||||||
"zod": "^3.24.1",
|
|
||||||
"react-hook-form": "^7.71.1",
|
|
||||||
"oidc-client-ts": "^3.4.1",
|
|
||||||
"react-oidc-context": "^3.3.0",
|
|
||||||
"@radix-ui/react-avatar": "^1.1.4",
|
"@radix-ui/react-avatar": "^1.1.4",
|
||||||
"@radix-ui/react-dialog": "^1.1.15",
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||||
"@radix-ui/react-scroll-area": "^1.1.2",
|
"@radix-ui/react-scroll-area": "^1.1.2",
|
||||||
"@radix-ui/react-select": "^2.2.6",
|
"@radix-ui/react-select": "^2.2.6",
|
||||||
"@radix-ui/react-slot": "^1.1.2",
|
"@radix-ui/react-slot": "^1.1.2",
|
||||||
"@radix-ui/react-switch": "^1.1.2"
|
"@radix-ui/react-switch": "^1.1.2",
|
||||||
|
"@tanstack/react-query": "^5.66.8",
|
||||||
|
"@tanstack/react-query-devtools": "^5.66.8",
|
||||||
|
"axios": "^1.7.9",
|
||||||
|
"class-variance-authority": "^0.7.1",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"lucide-react": "^0.563.0",
|
||||||
|
"oidc-client-ts": "^3.4.1",
|
||||||
|
"react": "^19.2.0",
|
||||||
|
"react-dom": "^19.2.0",
|
||||||
|
"react-hook-form": "^7.71.1",
|
||||||
|
"react-oidc-context": "^3.3.0",
|
||||||
|
"react-router-dom": "^6.28.2",
|
||||||
|
"tailwind-merge": "^3.4.0",
|
||||||
|
"zod": "^3.24.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
320
common/pnpm-lock.yaml
generated
320
common/pnpm-lock.yaml
generated
@@ -73,8 +73,8 @@ importers:
|
|||||||
version: 3.25.76
|
version: 3.25.76
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@biomejs/biome':
|
'@biomejs/biome':
|
||||||
specifier: ^1.9.4
|
specifier: 2.4.16
|
||||||
version: 1.9.4
|
version: 2.4.16
|
||||||
'@playwright/test':
|
'@playwright/test':
|
||||||
specifier: ^1.58.0
|
specifier: ^1.58.0
|
||||||
version: 1.60.0
|
version: 1.60.0
|
||||||
@@ -113,7 +113,7 @@ importers:
|
|||||||
version: 8.0.12(@types/node@24.12.4)(jiti@1.21.7)
|
version: 8.0.12(@types/node@24.12.4)(jiti@1.21.7)
|
||||||
vitest:
|
vitest:
|
||||||
specifier: ^4.1.5
|
specifier: ^4.1.5
|
||||||
version: 4.1.6(@types/node@24.12.4)(jsdom@28.1.0)(vite@8.0.12(@types/node@24.12.4)(jiti@1.21.7))
|
version: 4.1.6(@types/node@24.12.4)(@vitest/coverage-v8@4.1.6)(jsdom@28.1.0)(vite@8.0.12(@types/node@24.12.4)(jiti@1.21.7))
|
||||||
|
|
||||||
../adminfront:
|
../adminfront:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -184,6 +184,9 @@ importers:
|
|||||||
specifier: ^4.4.3
|
specifier: ^4.4.3
|
||||||
version: 4.4.3
|
version: 4.4.3
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
'@biomejs/biome':
|
||||||
|
specifier: 2.4.16
|
||||||
|
version: 2.4.16
|
||||||
'@playwright/test':
|
'@playwright/test':
|
||||||
specifier: ^1.60.0
|
specifier: ^1.60.0
|
||||||
version: 1.60.0
|
version: 1.60.0
|
||||||
@@ -211,12 +214,18 @@ importers:
|
|||||||
'@vitejs/plugin-react':
|
'@vitejs/plugin-react':
|
||||||
specifier: ^6.0.1
|
specifier: ^6.0.1
|
||||||
version: 6.0.1(vite@8.0.14(@types/node@25.7.0)(jiti@1.21.7))
|
version: 6.0.1(vite@8.0.14(@types/node@25.7.0)(jiti@1.21.7))
|
||||||
|
'@vitest/coverage-v8':
|
||||||
|
specifier: 4.1.6
|
||||||
|
version: 4.1.6(vitest@4.1.6)
|
||||||
autoprefixer:
|
autoprefixer:
|
||||||
specifier: ^10.5.0
|
specifier: ^10.5.0
|
||||||
version: 10.5.0(postcss@8.5.14)
|
version: 10.5.0(postcss@8.5.14)
|
||||||
jsdom:
|
jsdom:
|
||||||
specifier: ^28.1.0
|
specifier: ^28.1.0
|
||||||
version: 28.1.0
|
version: 28.1.0
|
||||||
|
playwright:
|
||||||
|
specifier: 1.60.0
|
||||||
|
version: 1.60.0
|
||||||
postcss:
|
postcss:
|
||||||
specifier: ^8.5.14
|
specifier: ^8.5.14
|
||||||
version: 8.5.14
|
version: 8.5.14
|
||||||
@@ -234,7 +243,7 @@ importers:
|
|||||||
version: 8.0.14(@types/node@25.7.0)(jiti@1.21.7)
|
version: 8.0.14(@types/node@25.7.0)(jiti@1.21.7)
|
||||||
vitest:
|
vitest:
|
||||||
specifier: ^4.1.6
|
specifier: ^4.1.6
|
||||||
version: 4.1.6(@types/node@25.7.0)(jsdom@28.1.0)(vite@8.0.14(@types/node@25.7.0)(jiti@1.21.7))
|
version: 4.1.6(@types/node@25.7.0)(@vitest/coverage-v8@4.1.6)(jsdom@28.1.0)(vite@8.0.14(@types/node@25.7.0)(jiti@1.21.7))
|
||||||
|
|
||||||
../devfront:
|
../devfront:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -302,6 +311,9 @@ importers:
|
|||||||
specifier: ^4.4.3
|
specifier: ^4.4.3
|
||||||
version: 4.4.3
|
version: 4.4.3
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
'@biomejs/biome':
|
||||||
|
specifier: 2.4.16
|
||||||
|
version: 2.4.16
|
||||||
'@playwright/test':
|
'@playwright/test':
|
||||||
specifier: ^1.60.0
|
specifier: ^1.60.0
|
||||||
version: 1.60.0
|
version: 1.60.0
|
||||||
@@ -316,10 +328,16 @@ importers:
|
|||||||
version: 19.2.3(@types/react@19.2.14)
|
version: 19.2.3(@types/react@19.2.14)
|
||||||
'@vitejs/plugin-react':
|
'@vitejs/plugin-react':
|
||||||
specifier: ^6.0.1
|
specifier: ^6.0.1
|
||||||
version: 6.0.1(vite@8.0.12(@types/node@25.7.0)(jiti@1.21.7))
|
version: 6.0.1(vite@8.0.14(@types/node@25.7.0)(jiti@1.21.7))
|
||||||
|
'@vitest/coverage-v8':
|
||||||
|
specifier: 4.1.6
|
||||||
|
version: 4.1.6(vitest@4.1.6)
|
||||||
autoprefixer:
|
autoprefixer:
|
||||||
specifier: ^10.5.0
|
specifier: ^10.5.0
|
||||||
version: 10.5.0(postcss@8.5.14)
|
version: 10.5.0(postcss@8.5.14)
|
||||||
|
jsdom:
|
||||||
|
specifier: ^28.1.0
|
||||||
|
version: 28.1.0
|
||||||
postcss:
|
postcss:
|
||||||
specifier: ^8.5.14
|
specifier: ^8.5.14
|
||||||
version: 8.5.14
|
version: 8.5.14
|
||||||
@@ -333,11 +351,11 @@ importers:
|
|||||||
specifier: ^6.0.3
|
specifier: ^6.0.3
|
||||||
version: 6.0.3
|
version: 6.0.3
|
||||||
vite:
|
vite:
|
||||||
specifier: ^8.0.12
|
specifier: ^8.0.14
|
||||||
version: 8.0.12(@types/node@25.7.0)(jiti@1.21.7)
|
version: 8.0.14(@types/node@25.7.0)(jiti@1.21.7)
|
||||||
vitest:
|
vitest:
|
||||||
specifier: ^4.1.6
|
specifier: ^4.1.6
|
||||||
version: 4.1.6(@types/node@25.7.0)(jsdom@28.1.0)(vite@8.0.12(@types/node@25.7.0)(jiti@1.21.7))
|
version: 4.1.6(@types/node@25.7.0)(@vitest/coverage-v8@4.1.6)(jsdom@28.1.0)(vite@8.0.14(@types/node@25.7.0)(jiti@1.21.7))
|
||||||
|
|
||||||
../orgfront:
|
../orgfront:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -408,6 +426,9 @@ importers:
|
|||||||
specifier: ^4.4.3
|
specifier: ^4.4.3
|
||||||
version: 4.4.3
|
version: 4.4.3
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
'@biomejs/biome':
|
||||||
|
specifier: 2.4.16
|
||||||
|
version: 2.4.16
|
||||||
'@playwright/test':
|
'@playwright/test':
|
||||||
specifier: ^1.60.0
|
specifier: ^1.60.0
|
||||||
version: 1.60.0
|
version: 1.60.0
|
||||||
@@ -422,7 +443,10 @@ importers:
|
|||||||
version: 19.2.3(@types/react@19.2.14)
|
version: 19.2.3(@types/react@19.2.14)
|
||||||
'@vitejs/plugin-react':
|
'@vitejs/plugin-react':
|
||||||
specifier: ^6.0.1
|
specifier: ^6.0.1
|
||||||
version: 6.0.1(vite@8.0.12(@types/node@25.7.0)(jiti@1.21.7))
|
version: 6.0.1(vite@8.0.14(@types/node@25.7.0)(jiti@1.21.7))
|
||||||
|
'@vitest/coverage-v8':
|
||||||
|
specifier: 4.1.6
|
||||||
|
version: 4.1.6(vitest@4.1.6)
|
||||||
autoprefixer:
|
autoprefixer:
|
||||||
specifier: ^10.5.0
|
specifier: ^10.5.0
|
||||||
version: 10.5.0(postcss@8.5.14)
|
version: 10.5.0(postcss@8.5.14)
|
||||||
@@ -442,11 +466,11 @@ importers:
|
|||||||
specifier: ^6.0.3
|
specifier: ^6.0.3
|
||||||
version: 6.0.3
|
version: 6.0.3
|
||||||
vite:
|
vite:
|
||||||
specifier: ^8.0.12
|
specifier: ^8.0.14
|
||||||
version: 8.0.12(@types/node@25.7.0)(jiti@1.21.7)
|
version: 8.0.14(@types/node@25.7.0)(jiti@1.21.7)
|
||||||
vitest:
|
vitest:
|
||||||
specifier: ^4.1.6
|
specifier: ^4.1.6
|
||||||
version: 4.1.6(@types/node@25.7.0)(jsdom@28.1.0)(vite@8.0.12(@types/node@25.7.0)(jiti@1.21.7))
|
version: 4.1.6(@types/node@25.7.0)(@vitest/coverage-v8@4.1.6)(jsdom@28.1.0)(vite@8.0.14(@types/node@25.7.0)(jiti@1.21.7))
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
@@ -478,67 +502,84 @@ packages:
|
|||||||
resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
|
resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
'@babel/helper-validator-identifier@7.28.5':
|
'@babel/helper-string-parser@7.29.7':
|
||||||
resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
|
resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
|
'@babel/helper-validator-identifier@7.29.7':
|
||||||
|
resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==}
|
||||||
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
|
'@babel/parser@7.29.7':
|
||||||
|
resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==}
|
||||||
|
engines: {node: '>=6.0.0'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
'@babel/runtime@7.29.2':
|
'@babel/runtime@7.29.2':
|
||||||
resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==}
|
resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
'@biomejs/biome@1.9.4':
|
'@babel/types@7.29.7':
|
||||||
resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==}
|
resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==}
|
||||||
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
|
'@bcoe/v8-coverage@1.0.2':
|
||||||
|
resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
'@biomejs/biome@2.4.16':
|
||||||
|
resolution: {integrity: sha512-x9ajFh1zChVybCiM3TN6OD4phAqLgtPZjFrZF+aTMYCPjwBO+k529TX7PPsAqtGNLeV4UgzwQnowEgS7bGmzcA==}
|
||||||
engines: {node: '>=14.21.3'}
|
engines: {node: '>=14.21.3'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
'@biomejs/cli-darwin-arm64@1.9.4':
|
'@biomejs/cli-darwin-arm64@2.4.16':
|
||||||
resolution: {integrity: sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==}
|
resolution: {integrity: sha512-wxPvu4XOA85YJk9ixSWUmq/QBHbid85BISbOAqqBM/5xQpPk9ayjk5375tOlSC0BeCwNSbPFafQBm+vBumXq0A==}
|
||||||
engines: {node: '>=14.21.3'}
|
engines: {node: '>=14.21.3'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@biomejs/cli-darwin-x64@1.9.4':
|
'@biomejs/cli-darwin-x64@2.4.16':
|
||||||
resolution: {integrity: sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==}
|
resolution: {integrity: sha512-xFCqGPwYusQJp4N4NJLi1XJiZqjwFdjhT+KqtNy+Ug3qgfczqnTa6MSDvxJF6TkuDLoYJItMapz6tAf7kCekFw==}
|
||||||
engines: {node: '>=14.21.3'}
|
engines: {node: '>=14.21.3'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@biomejs/cli-linux-arm64-musl@1.9.4':
|
'@biomejs/cli-linux-arm64-musl@2.4.16':
|
||||||
resolution: {integrity: sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==}
|
resolution: {integrity: sha512-oYxnW0ARfJkr72ezzF2OR8N/rtkgLUQeYtF8cFhVswbknHxtTcmzSsanVJP8yQKnGpGpc2ck6c5zLvHahL6Cbg==}
|
||||||
engines: {node: '>=14.21.3'}
|
engines: {node: '>=14.21.3'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
libc: [musl]
|
||||||
|
|
||||||
'@biomejs/cli-linux-arm64@1.9.4':
|
'@biomejs/cli-linux-arm64@2.4.16':
|
||||||
resolution: {integrity: sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==}
|
resolution: {integrity: sha512-2kFb4//jxfZaP6D+Rj5VkHkxgyD9EoRAVBEQb8PKRv+s4NO2zYNJKXFaJmK1CmhufJOWEfpHKaRbOja7qjmdhQ==}
|
||||||
engines: {node: '>=14.21.3'}
|
engines: {node: '>=14.21.3'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@biomejs/cli-linux-x64-musl@1.9.4':
|
'@biomejs/cli-linux-x64-musl@2.4.16':
|
||||||
resolution: {integrity: sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==}
|
resolution: {integrity: sha512-iHDS+MCM65DPqWGu+ECC3uoALyj2H7F4nVUPxIPjz/PIl94EUu+EDfGZDzFP+NY1EOPVt9NQvwFqq7HdMmowdg==}
|
||||||
engines: {node: '>=14.21.3'}
|
engines: {node: '>=14.21.3'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
libc: [musl]
|
||||||
|
|
||||||
'@biomejs/cli-linux-x64@1.9.4':
|
'@biomejs/cli-linux-x64@2.4.16':
|
||||||
resolution: {integrity: sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==}
|
resolution: {integrity: sha512-NbcBbi/nJqn5baae6wqRXdS7Gadf2uRpehSh6vMSYpG8OhkXl/Xg8aorWrJ+9VWqAT5ml90alLvorkpMW0nBwQ==}
|
||||||
engines: {node: '>=14.21.3'}
|
engines: {node: '>=14.21.3'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@biomejs/cli-win32-arm64@1.9.4':
|
'@biomejs/cli-win32-arm64@2.4.16':
|
||||||
resolution: {integrity: sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==}
|
resolution: {integrity: sha512-0rgImMsNb5v/chhkIFe3wu7PEFClS6RBAYUijGL9UsYN3PanSaoK24HSSuSJb1pYbYYVjzAyZTl3gtjJ84BM8A==}
|
||||||
engines: {node: '>=14.21.3'}
|
engines: {node: '>=14.21.3'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@biomejs/cli-win32-x64@1.9.4':
|
'@biomejs/cli-win32-x64@2.4.16':
|
||||||
resolution: {integrity: sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==}
|
resolution: {integrity: sha512-Kp85jgoBHa05gix6UIRjfCDiUV3w/8VIdZ247VyyO2gEjaw12WEVhdIjlxp/AMzXxqxQwbxNTDVZ3Mwd2RG5rw==}
|
||||||
engines: {node: '>=14.21.3'}
|
engines: {node: '>=14.21.3'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
@@ -1381,6 +1422,15 @@ packages:
|
|||||||
babel-plugin-react-compiler:
|
babel-plugin-react-compiler:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@vitest/coverage-v8@4.1.6':
|
||||||
|
resolution: {integrity: sha512-36l628fQ/9a/8ihy97eOtEnvWQEdqULQOJtcaxtoNq0G1w3Mxd4szSahOaMM9/NGyZ+hyKcMtIW/WIxq0XQViQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@vitest/browser': 4.1.6
|
||||||
|
vitest: 4.1.6
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@vitest/browser':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@vitest/expect@4.1.6':
|
'@vitest/expect@4.1.6':
|
||||||
resolution: {integrity: sha512-7EHDquPthALSV0jhhjgEW8FXaviMx7rSqu8W6oqCoAuOhKov814P99QDV1pxMA3QPv21YudvJngIhjrNI4opLg==}
|
resolution: {integrity: sha512-7EHDquPthALSV0jhhjgEW8FXaviMx7rSqu8W6oqCoAuOhKov814P99QDV1pxMA3QPv21YudvJngIhjrNI4opLg==}
|
||||||
|
|
||||||
@@ -1460,6 +1510,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
|
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
ast-v8-to-istanbul@1.0.2:
|
||||||
|
resolution: {integrity: sha512-dKmJxJsGItLmc5CYZKuEjuG6GnBs6PG4gohMhyFOWKaNQoYCuRZJDECaBlHmcG0lv2wc2E0uU8lESmBEumC3DQ==}
|
||||||
|
|
||||||
asynckit@0.4.0:
|
asynckit@0.4.0:
|
||||||
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||||
|
|
||||||
@@ -1755,6 +1808,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
has-flag@4.0.0:
|
||||||
|
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
has-symbols@1.1.0:
|
has-symbols@1.1.0:
|
||||||
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
|
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -1771,6 +1828,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==}
|
resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==}
|
||||||
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
|
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
|
||||||
|
|
||||||
|
html-escaper@2.0.2:
|
||||||
|
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
|
||||||
|
|
||||||
http-proxy-agent@7.0.2:
|
http-proxy-agent@7.0.2:
|
||||||
resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
|
resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
|
||||||
engines: {node: '>= 14'}
|
engines: {node: '>= 14'}
|
||||||
@@ -1810,10 +1870,25 @@ packages:
|
|||||||
is-potential-custom-element-name@1.0.1:
|
is-potential-custom-element-name@1.0.1:
|
||||||
resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
|
resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
|
||||||
|
|
||||||
|
istanbul-lib-coverage@3.2.2:
|
||||||
|
resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
istanbul-lib-report@3.0.1:
|
||||||
|
resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
istanbul-reports@3.2.0:
|
||||||
|
resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
jiti@1.21.7:
|
jiti@1.21.7:
|
||||||
resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
|
resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
js-tokens@10.0.0:
|
||||||
|
resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==}
|
||||||
|
|
||||||
js-tokens@4.0.0:
|
js-tokens@4.0.0:
|
||||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||||
|
|
||||||
@@ -1932,6 +2007,13 @@ packages:
|
|||||||
magic-string@0.30.21:
|
magic-string@0.30.21:
|
||||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||||
|
|
||||||
|
magicast@0.5.3:
|
||||||
|
resolution: {integrity: sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==}
|
||||||
|
|
||||||
|
make-dir@4.0.0:
|
||||||
|
resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
math-intrinsics@1.1.0:
|
math-intrinsics@1.1.0:
|
||||||
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -2225,6 +2307,11 @@ packages:
|
|||||||
scheduler@0.27.0:
|
scheduler@0.27.0:
|
||||||
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
|
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
|
||||||
|
|
||||||
|
semver@7.8.1:
|
||||||
|
resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
set-cookie-parser@2.7.2:
|
set-cookie-parser@2.7.2:
|
||||||
resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==}
|
resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==}
|
||||||
|
|
||||||
@@ -2250,6 +2337,10 @@ packages:
|
|||||||
engines: {node: '>=16 || 14 >=14.17'}
|
engines: {node: '>=16 || 14 >=14.17'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
supports-color@7.2.0:
|
||||||
|
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
supports-preserve-symlinks-flag@1.0.0:
|
supports-preserve-symlinks-flag@1.0.0:
|
||||||
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
|
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -2577,47 +2668,60 @@ snapshots:
|
|||||||
|
|
||||||
'@babel/code-frame@7.29.0':
|
'@babel/code-frame@7.29.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/helper-validator-identifier': 7.28.5
|
'@babel/helper-validator-identifier': 7.29.7
|
||||||
js-tokens: 4.0.0
|
js-tokens: 4.0.0
|
||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
|
|
||||||
'@babel/helper-validator-identifier@7.28.5': {}
|
'@babel/helper-string-parser@7.29.7': {}
|
||||||
|
|
||||||
|
'@babel/helper-validator-identifier@7.29.7': {}
|
||||||
|
|
||||||
|
'@babel/parser@7.29.7':
|
||||||
|
dependencies:
|
||||||
|
'@babel/types': 7.29.7
|
||||||
|
|
||||||
'@babel/runtime@7.29.2': {}
|
'@babel/runtime@7.29.2': {}
|
||||||
|
|
||||||
'@biomejs/biome@1.9.4':
|
'@babel/types@7.29.7':
|
||||||
|
dependencies:
|
||||||
|
'@babel/helper-string-parser': 7.29.7
|
||||||
|
'@babel/helper-validator-identifier': 7.29.7
|
||||||
|
|
||||||
|
'@bcoe/v8-coverage@1.0.2': {}
|
||||||
|
|
||||||
|
'@biomejs/biome@2.4.16':
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@biomejs/cli-darwin-arm64': 1.9.4
|
'@biomejs/cli-darwin-arm64': 2.4.16
|
||||||
'@biomejs/cli-darwin-x64': 1.9.4
|
'@biomejs/cli-darwin-x64': 2.4.16
|
||||||
'@biomejs/cli-linux-arm64': 1.9.4
|
'@biomejs/cli-linux-arm64': 2.4.16
|
||||||
'@biomejs/cli-linux-arm64-musl': 1.9.4
|
'@biomejs/cli-linux-arm64-musl': 2.4.16
|
||||||
'@biomejs/cli-linux-x64': 1.9.4
|
'@biomejs/cli-linux-x64': 2.4.16
|
||||||
'@biomejs/cli-linux-x64-musl': 1.9.4
|
'@biomejs/cli-linux-x64-musl': 2.4.16
|
||||||
'@biomejs/cli-win32-arm64': 1.9.4
|
'@biomejs/cli-win32-arm64': 2.4.16
|
||||||
'@biomejs/cli-win32-x64': 1.9.4
|
'@biomejs/cli-win32-x64': 2.4.16
|
||||||
|
|
||||||
'@biomejs/cli-darwin-arm64@1.9.4':
|
'@biomejs/cli-darwin-arm64@2.4.16':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@biomejs/cli-darwin-x64@1.9.4':
|
'@biomejs/cli-darwin-x64@2.4.16':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@biomejs/cli-linux-arm64-musl@1.9.4':
|
'@biomejs/cli-linux-arm64-musl@2.4.16':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@biomejs/cli-linux-arm64@1.9.4':
|
'@biomejs/cli-linux-arm64@2.4.16':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@biomejs/cli-linux-x64-musl@1.9.4':
|
'@biomejs/cli-linux-x64-musl@2.4.16':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@biomejs/cli-linux-x64@1.9.4':
|
'@biomejs/cli-linux-x64@2.4.16':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@biomejs/cli-win32-arm64@1.9.4':
|
'@biomejs/cli-win32-arm64@2.4.16':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@biomejs/cli-win32-x64@1.9.4':
|
'@biomejs/cli-win32-x64@2.4.16':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@bramus/specificity@2.4.2':
|
'@bramus/specificity@2.4.2':
|
||||||
@@ -3340,16 +3444,25 @@ snapshots:
|
|||||||
'@rolldown/pluginutils': 1.0.0-rc.7
|
'@rolldown/pluginutils': 1.0.0-rc.7
|
||||||
vite: 8.0.12(@types/node@24.12.4)(jiti@1.21.7)
|
vite: 8.0.12(@types/node@24.12.4)(jiti@1.21.7)
|
||||||
|
|
||||||
'@vitejs/plugin-react@6.0.1(vite@8.0.12(@types/node@25.7.0)(jiti@1.21.7))':
|
|
||||||
dependencies:
|
|
||||||
'@rolldown/pluginutils': 1.0.0-rc.7
|
|
||||||
vite: 8.0.12(@types/node@25.7.0)(jiti@1.21.7)
|
|
||||||
|
|
||||||
'@vitejs/plugin-react@6.0.1(vite@8.0.14(@types/node@25.7.0)(jiti@1.21.7))':
|
'@vitejs/plugin-react@6.0.1(vite@8.0.14(@types/node@25.7.0)(jiti@1.21.7))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@rolldown/pluginutils': 1.0.0-rc.7
|
'@rolldown/pluginutils': 1.0.0-rc.7
|
||||||
vite: 8.0.14(@types/node@25.7.0)(jiti@1.21.7)
|
vite: 8.0.14(@types/node@25.7.0)(jiti@1.21.7)
|
||||||
|
|
||||||
|
'@vitest/coverage-v8@4.1.6(vitest@4.1.6)':
|
||||||
|
dependencies:
|
||||||
|
'@bcoe/v8-coverage': 1.0.2
|
||||||
|
'@vitest/utils': 4.1.6
|
||||||
|
ast-v8-to-istanbul: 1.0.2
|
||||||
|
istanbul-lib-coverage: 3.2.2
|
||||||
|
istanbul-lib-report: 3.0.1
|
||||||
|
istanbul-reports: 3.2.0
|
||||||
|
magicast: 0.5.3
|
||||||
|
obug: 2.1.1
|
||||||
|
std-env: 4.1.0
|
||||||
|
tinyrainbow: 3.1.0
|
||||||
|
vitest: 4.1.6(@types/node@25.7.0)(@vitest/coverage-v8@4.1.6)(jsdom@28.1.0)(vite@8.0.14(@types/node@25.7.0)(jiti@1.21.7))
|
||||||
|
|
||||||
'@vitest/expect@4.1.6':
|
'@vitest/expect@4.1.6':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@standard-schema/spec': 1.1.0
|
'@standard-schema/spec': 1.1.0
|
||||||
@@ -3367,14 +3480,6 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
vite: 8.0.12(@types/node@24.12.4)(jiti@1.21.7)
|
vite: 8.0.12(@types/node@24.12.4)(jiti@1.21.7)
|
||||||
|
|
||||||
'@vitest/mocker@4.1.6(vite@8.0.12(@types/node@25.7.0)(jiti@1.21.7))':
|
|
||||||
dependencies:
|
|
||||||
'@vitest/spy': 4.1.6
|
|
||||||
estree-walker: 3.0.3
|
|
||||||
magic-string: 0.30.21
|
|
||||||
optionalDependencies:
|
|
||||||
vite: 8.0.12(@types/node@25.7.0)(jiti@1.21.7)
|
|
||||||
|
|
||||||
'@vitest/mocker@4.1.6(vite@8.0.14(@types/node@25.7.0)(jiti@1.21.7))':
|
'@vitest/mocker@4.1.6(vite@8.0.14(@types/node@25.7.0)(jiti@1.21.7))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vitest/spy': 4.1.6
|
'@vitest/spy': 4.1.6
|
||||||
@@ -3463,6 +3568,12 @@ snapshots:
|
|||||||
|
|
||||||
assertion-error@2.0.1: {}
|
assertion-error@2.0.1: {}
|
||||||
|
|
||||||
|
ast-v8-to-istanbul@1.0.2:
|
||||||
|
dependencies:
|
||||||
|
'@jridgewell/trace-mapping': 0.3.31
|
||||||
|
estree-walker: 3.0.3
|
||||||
|
js-tokens: 10.0.0
|
||||||
|
|
||||||
asynckit@0.4.0: {}
|
asynckit@0.4.0: {}
|
||||||
|
|
||||||
autoprefixer@10.5.0(postcss@8.5.14):
|
autoprefixer@10.5.0(postcss@8.5.14):
|
||||||
@@ -3741,6 +3852,8 @@ snapshots:
|
|||||||
|
|
||||||
gopd@1.2.0: {}
|
gopd@1.2.0: {}
|
||||||
|
|
||||||
|
has-flag@4.0.0: {}
|
||||||
|
|
||||||
has-symbols@1.1.0: {}
|
has-symbols@1.1.0: {}
|
||||||
|
|
||||||
has-tostringtag@1.0.2:
|
has-tostringtag@1.0.2:
|
||||||
@@ -3757,6 +3870,8 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@noble/hashes'
|
- '@noble/hashes'
|
||||||
|
|
||||||
|
html-escaper@2.0.2: {}
|
||||||
|
|
||||||
http-proxy-agent@7.0.2:
|
http-proxy-agent@7.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base: 7.1.4
|
agent-base: 7.1.4
|
||||||
@@ -3798,8 +3913,23 @@ snapshots:
|
|||||||
|
|
||||||
is-potential-custom-element-name@1.0.1: {}
|
is-potential-custom-element-name@1.0.1: {}
|
||||||
|
|
||||||
|
istanbul-lib-coverage@3.2.2: {}
|
||||||
|
|
||||||
|
istanbul-lib-report@3.0.1:
|
||||||
|
dependencies:
|
||||||
|
istanbul-lib-coverage: 3.2.2
|
||||||
|
make-dir: 4.0.0
|
||||||
|
supports-color: 7.2.0
|
||||||
|
|
||||||
|
istanbul-reports@3.2.0:
|
||||||
|
dependencies:
|
||||||
|
html-escaper: 2.0.2
|
||||||
|
istanbul-lib-report: 3.0.1
|
||||||
|
|
||||||
jiti@1.21.7: {}
|
jiti@1.21.7: {}
|
||||||
|
|
||||||
|
js-tokens@10.0.0: {}
|
||||||
|
|
||||||
js-tokens@4.0.0: {}
|
js-tokens@4.0.0: {}
|
||||||
|
|
||||||
jsdom@28.1.0:
|
jsdom@28.1.0:
|
||||||
@@ -3900,6 +4030,16 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/sourcemap-codec': 1.5.5
|
'@jridgewell/sourcemap-codec': 1.5.5
|
||||||
|
|
||||||
|
magicast@0.5.3:
|
||||||
|
dependencies:
|
||||||
|
'@babel/parser': 7.29.7
|
||||||
|
'@babel/types': 7.29.7
|
||||||
|
source-map-js: 1.2.1
|
||||||
|
|
||||||
|
make-dir@4.0.0:
|
||||||
|
dependencies:
|
||||||
|
semver: 7.8.1
|
||||||
|
|
||||||
math-intrinsics@1.1.0: {}
|
math-intrinsics@1.1.0: {}
|
||||||
|
|
||||||
mdn-data@2.27.1: {}
|
mdn-data@2.27.1: {}
|
||||||
@@ -4171,6 +4311,8 @@ snapshots:
|
|||||||
|
|
||||||
scheduler@0.27.0: {}
|
scheduler@0.27.0: {}
|
||||||
|
|
||||||
|
semver@7.8.1: {}
|
||||||
|
|
||||||
set-cookie-parser@2.7.2: {}
|
set-cookie-parser@2.7.2: {}
|
||||||
|
|
||||||
siginfo@2.0.0: {}
|
siginfo@2.0.0: {}
|
||||||
@@ -4195,6 +4337,10 @@ snapshots:
|
|||||||
tinyglobby: 0.2.16
|
tinyglobby: 0.2.16
|
||||||
ts-interface-checker: 0.1.13
|
ts-interface-checker: 0.1.13
|
||||||
|
|
||||||
|
supports-color@7.2.0:
|
||||||
|
dependencies:
|
||||||
|
has-flag: 4.0.0
|
||||||
|
|
||||||
supports-preserve-symlinks-flag@1.0.0: {}
|
supports-preserve-symlinks-flag@1.0.0: {}
|
||||||
|
|
||||||
symbol-tree@3.2.4: {}
|
symbol-tree@3.2.4: {}
|
||||||
@@ -4323,18 +4469,6 @@ snapshots:
|
|||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
jiti: 1.21.7
|
jiti: 1.21.7
|
||||||
|
|
||||||
vite@8.0.12(@types/node@25.7.0)(jiti@1.21.7):
|
|
||||||
dependencies:
|
|
||||||
lightningcss: 1.32.0
|
|
||||||
picomatch: 4.0.4
|
|
||||||
postcss: 8.5.14
|
|
||||||
rolldown: 1.0.0
|
|
||||||
tinyglobby: 0.2.16
|
|
||||||
optionalDependencies:
|
|
||||||
'@types/node': 25.7.0
|
|
||||||
fsevents: 2.3.3
|
|
||||||
jiti: 1.21.7
|
|
||||||
|
|
||||||
vite@8.0.14(@types/node@25.7.0)(jiti@1.21.7):
|
vite@8.0.14(@types/node@25.7.0)(jiti@1.21.7):
|
||||||
dependencies:
|
dependencies:
|
||||||
lightningcss: 1.32.0
|
lightningcss: 1.32.0
|
||||||
@@ -4347,7 +4481,7 @@ snapshots:
|
|||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
jiti: 1.21.7
|
jiti: 1.21.7
|
||||||
|
|
||||||
vitest@4.1.6(@types/node@24.12.4)(jsdom@28.1.0)(vite@8.0.12(@types/node@24.12.4)(jiti@1.21.7)):
|
vitest@4.1.6(@types/node@24.12.4)(@vitest/coverage-v8@4.1.6)(jsdom@28.1.0)(vite@8.0.12(@types/node@24.12.4)(jiti@1.21.7)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vitest/expect': 4.1.6
|
'@vitest/expect': 4.1.6
|
||||||
'@vitest/mocker': 4.1.6(vite@8.0.12(@types/node@24.12.4)(jiti@1.21.7))
|
'@vitest/mocker': 4.1.6(vite@8.0.12(@types/node@24.12.4)(jiti@1.21.7))
|
||||||
@@ -4371,39 +4505,12 @@ snapshots:
|
|||||||
why-is-node-running: 2.3.0
|
why-is-node-running: 2.3.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/node': 24.12.4
|
'@types/node': 24.12.4
|
||||||
|
'@vitest/coverage-v8': 4.1.6(vitest@4.1.6)
|
||||||
jsdom: 28.1.0
|
jsdom: 28.1.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- msw
|
- msw
|
||||||
|
|
||||||
vitest@4.1.6(@types/node@25.7.0)(jsdom@28.1.0)(vite@8.0.12(@types/node@25.7.0)(jiti@1.21.7)):
|
vitest@4.1.6(@types/node@25.7.0)(@vitest/coverage-v8@4.1.6)(jsdom@28.1.0)(vite@8.0.14(@types/node@25.7.0)(jiti@1.21.7)):
|
||||||
dependencies:
|
|
||||||
'@vitest/expect': 4.1.6
|
|
||||||
'@vitest/mocker': 4.1.6(vite@8.0.12(@types/node@25.7.0)(jiti@1.21.7))
|
|
||||||
'@vitest/pretty-format': 4.1.6
|
|
||||||
'@vitest/runner': 4.1.6
|
|
||||||
'@vitest/snapshot': 4.1.6
|
|
||||||
'@vitest/spy': 4.1.6
|
|
||||||
'@vitest/utils': 4.1.6
|
|
||||||
es-module-lexer: 2.1.0
|
|
||||||
expect-type: 1.3.0
|
|
||||||
magic-string: 0.30.21
|
|
||||||
obug: 2.1.1
|
|
||||||
pathe: 2.0.3
|
|
||||||
picomatch: 4.0.4
|
|
||||||
std-env: 4.1.0
|
|
||||||
tinybench: 2.9.0
|
|
||||||
tinyexec: 1.1.2
|
|
||||||
tinyglobby: 0.2.16
|
|
||||||
tinyrainbow: 3.1.0
|
|
||||||
vite: 8.0.12(@types/node@25.7.0)(jiti@1.21.7)
|
|
||||||
why-is-node-running: 2.3.0
|
|
||||||
optionalDependencies:
|
|
||||||
'@types/node': 25.7.0
|
|
||||||
jsdom: 28.1.0
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- msw
|
|
||||||
|
|
||||||
vitest@4.1.6(@types/node@25.7.0)(jsdom@28.1.0)(vite@8.0.14(@types/node@25.7.0)(jiti@1.21.7)):
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vitest/expect': 4.1.6
|
'@vitest/expect': 4.1.6
|
||||||
'@vitest/mocker': 4.1.6(vite@8.0.14(@types/node@25.7.0)(jiti@1.21.7))
|
'@vitest/mocker': 4.1.6(vite@8.0.14(@types/node@25.7.0)(jiti@1.21.7))
|
||||||
@@ -4427,6 +4534,7 @@ snapshots:
|
|||||||
why-is-node-running: 2.3.0
|
why-is-node-running: 2.3.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/node': 25.7.0
|
'@types/node': 25.7.0
|
||||||
|
'@vitest/coverage-v8': 4.1.6(vitest@4.1.6)
|
||||||
jsdom: 28.1.0
|
jsdom: 28.1.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- msw
|
- msw
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
SESSION_EXPIRY_STORAGE_KEY,
|
|
||||||
readSessionExpiryEnabled,
|
readSessionExpiryEnabled,
|
||||||
|
SESSION_EXPIRY_STORAGE_KEY,
|
||||||
writeSessionExpiryEnabled,
|
writeSessionExpiryEnabled,
|
||||||
} from "../core/session";
|
} from "../core/session";
|
||||||
|
|
||||||
@@ -27,8 +27,8 @@ type ShellProfileSummaryParams = {
|
|||||||
|
|
||||||
export const SHELL_THEME_STORAGE_KEY = "admin_theme";
|
export const SHELL_THEME_STORAGE_KEY = "admin_theme";
|
||||||
export const SHELL_SESSION_EXPIRY_STORAGE_KEY = SESSION_EXPIRY_STORAGE_KEY;
|
export const SHELL_SESSION_EXPIRY_STORAGE_KEY = SESSION_EXPIRY_STORAGE_KEY;
|
||||||
export { AppSidebar } from "./AppSidebar";
|
|
||||||
export type { ShellSidebarNavItem } from "./AppSidebar";
|
export type { ShellSidebarNavItem } from "./AppSidebar";
|
||||||
|
export { AppSidebar } from "./AppSidebar";
|
||||||
export { shellLayoutClasses } from "./layout";
|
export { shellLayoutClasses } from "./layout";
|
||||||
|
|
||||||
export function readShellTheme(): ShellTheme {
|
export function readShellTheme(): ShellTheme {
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
{
|
{
|
||||||
"extends": ["../common/config/biome.base.json"],
|
"root": true,
|
||||||
"files": {
|
"extends": ["../common/config/biome.base.json"]
|
||||||
"ignore": [".vite"]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
1164
devfront/package-lock.json
generated
1164
devfront/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -12,22 +12,26 @@
|
|||||||
"lint": "biome check .",
|
"lint": "biome check .",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"test": "playwright test",
|
"test": "playwright test",
|
||||||
|
"test:coverage": "vitest run --coverage",
|
||||||
"test:unit": "vitest run",
|
"test:unit": "vitest run",
|
||||||
"test:roles": "playwright test tests/devfront-role-switch-report.spec.ts",
|
"test:roles": "playwright test tests/devfront-role-switch-report.spec.ts",
|
||||||
"test:ui": "playwright test --ui"
|
"test:ui": "playwright test --ui"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@biomejs/biome": "2.4.16",
|
||||||
"@playwright/test": "^1.60.0",
|
"@playwright/test": "^1.60.0",
|
||||||
"@types/node": "^25.7.0",
|
"@types/node": "^25.7.0",
|
||||||
"@types/react": "^19.2.14",
|
"@types/react": "^19.2.14",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
|
"@vitest/coverage-v8": "4.1.6",
|
||||||
"@vitejs/plugin-react": "^6.0.1",
|
"@vitejs/plugin-react": "^6.0.1",
|
||||||
"autoprefixer": "^10.5.0",
|
"autoprefixer": "^10.5.0",
|
||||||
|
"jsdom": "^28.1.0",
|
||||||
"postcss": "^8.5.14",
|
"postcss": "^8.5.14",
|
||||||
"tailwindcss": "^3.4.19",
|
"tailwindcss": "^3.4.19",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"typescript": "^6.0.3",
|
"typescript": "^6.0.3",
|
||||||
"vite": "^8.0.12",
|
"vite": "^8.0.14",
|
||||||
"vitest": "^4.1.6"
|
"vitest": "^4.1.6"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
615
devfront/pnpm-lock.yaml
generated
615
devfront/pnpm-lock.yaml
generated
@@ -72,6 +72,9 @@ importers:
|
|||||||
specifier: ^4.4.3
|
specifier: ^4.4.3
|
||||||
version: 4.4.3
|
version: 4.4.3
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
'@biomejs/biome':
|
||||||
|
specifier: 2.4.16
|
||||||
|
version: 2.4.16
|
||||||
'@playwright/test':
|
'@playwright/test':
|
||||||
specifier: ^1.60.0
|
specifier: ^1.60.0
|
||||||
version: 1.60.0
|
version: 1.60.0
|
||||||
@@ -87,9 +90,15 @@ importers:
|
|||||||
'@vitejs/plugin-react':
|
'@vitejs/plugin-react':
|
||||||
specifier: ^6.0.1
|
specifier: ^6.0.1
|
||||||
version: 6.0.1(vite@8.0.13(@types/node@25.7.0)(jiti@1.21.7))
|
version: 6.0.1(vite@8.0.13(@types/node@25.7.0)(jiti@1.21.7))
|
||||||
|
'@vitest/coverage-v8':
|
||||||
|
specifier: 4.1.6
|
||||||
|
version: 4.1.6(vitest@4.1.6)
|
||||||
autoprefixer:
|
autoprefixer:
|
||||||
specifier: ^10.5.0
|
specifier: ^10.5.0
|
||||||
version: 10.5.0(postcss@8.5.14)
|
version: 10.5.0(postcss@8.5.14)
|
||||||
|
jsdom:
|
||||||
|
specifier: ^28.1.0
|
||||||
|
version: 28.1.0
|
||||||
postcss:
|
postcss:
|
||||||
specifier: ^8.5.14
|
specifier: ^8.5.14
|
||||||
version: 8.5.14
|
version: 8.5.14
|
||||||
@@ -107,14 +116,149 @@ importers:
|
|||||||
version: 8.0.13(@types/node@25.7.0)(jiti@1.21.7)
|
version: 8.0.13(@types/node@25.7.0)(jiti@1.21.7)
|
||||||
vitest:
|
vitest:
|
||||||
specifier: ^4.1.6
|
specifier: ^4.1.6
|
||||||
version: 4.1.6(@types/node@25.7.0)(vite@8.0.13(@types/node@25.7.0)(jiti@1.21.7))
|
version: 4.1.6(@types/node@25.7.0)(@vitest/coverage-v8@4.1.6)(jsdom@28.1.0)(vite@8.0.13(@types/node@25.7.0)(jiti@1.21.7))
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
|
'@acemir/cssom@0.9.31':
|
||||||
|
resolution: {integrity: sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==}
|
||||||
|
|
||||||
'@alloc/quick-lru@5.2.0':
|
'@alloc/quick-lru@5.2.0':
|
||||||
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
|
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
'@asamuzakjp/css-color@5.1.11':
|
||||||
|
resolution: {integrity: sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==}
|
||||||
|
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
|
||||||
|
|
||||||
|
'@asamuzakjp/dom-selector@6.8.1':
|
||||||
|
resolution: {integrity: sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==}
|
||||||
|
|
||||||
|
'@asamuzakjp/generational-cache@1.0.1':
|
||||||
|
resolution: {integrity: sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==}
|
||||||
|
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
|
||||||
|
|
||||||
|
'@asamuzakjp/nwsapi@2.3.9':
|
||||||
|
resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==}
|
||||||
|
|
||||||
|
'@babel/helper-string-parser@7.29.7':
|
||||||
|
resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==}
|
||||||
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
|
'@babel/helper-validator-identifier@7.29.7':
|
||||||
|
resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==}
|
||||||
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
|
'@babel/parser@7.29.7':
|
||||||
|
resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==}
|
||||||
|
engines: {node: '>=6.0.0'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
'@babel/types@7.29.7':
|
||||||
|
resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==}
|
||||||
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
|
'@bcoe/v8-coverage@1.0.2':
|
||||||
|
resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
'@biomejs/biome@2.4.16':
|
||||||
|
resolution: {integrity: sha512-x9ajFh1zChVybCiM3TN6OD4phAqLgtPZjFrZF+aTMYCPjwBO+k529TX7PPsAqtGNLeV4UgzwQnowEgS7bGmzcA==}
|
||||||
|
engines: {node: '>=14.21.3'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
'@biomejs/cli-darwin-arm64@2.4.16':
|
||||||
|
resolution: {integrity: sha512-wxPvu4XOA85YJk9ixSWUmq/QBHbid85BISbOAqqBM/5xQpPk9ayjk5375tOlSC0BeCwNSbPFafQBm+vBumXq0A==}
|
||||||
|
engines: {node: '>=14.21.3'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@biomejs/cli-darwin-x64@2.4.16':
|
||||||
|
resolution: {integrity: sha512-xFCqGPwYusQJp4N4NJLi1XJiZqjwFdjhT+KqtNy+Ug3qgfczqnTa6MSDvxJF6TkuDLoYJItMapz6tAf7kCekFw==}
|
||||||
|
engines: {node: '>=14.21.3'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@biomejs/cli-linux-arm64-musl@2.4.16':
|
||||||
|
resolution: {integrity: sha512-oYxnW0ARfJkr72ezzF2OR8N/rtkgLUQeYtF8cFhVswbknHxtTcmzSsanVJP8yQKnGpGpc2ck6c5zLvHahL6Cbg==}
|
||||||
|
engines: {node: '>=14.21.3'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
|
'@biomejs/cli-linux-arm64@2.4.16':
|
||||||
|
resolution: {integrity: sha512-2kFb4//jxfZaP6D+Rj5VkHkxgyD9EoRAVBEQb8PKRv+s4NO2zYNJKXFaJmK1CmhufJOWEfpHKaRbOja7qjmdhQ==}
|
||||||
|
engines: {node: '>=14.21.3'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
|
'@biomejs/cli-linux-x64-musl@2.4.16':
|
||||||
|
resolution: {integrity: sha512-iHDS+MCM65DPqWGu+ECC3uoALyj2H7F4nVUPxIPjz/PIl94EUu+EDfGZDzFP+NY1EOPVt9NQvwFqq7HdMmowdg==}
|
||||||
|
engines: {node: '>=14.21.3'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
|
'@biomejs/cli-linux-x64@2.4.16':
|
||||||
|
resolution: {integrity: sha512-NbcBbi/nJqn5baae6wqRXdS7Gadf2uRpehSh6vMSYpG8OhkXl/Xg8aorWrJ+9VWqAT5ml90alLvorkpMW0nBwQ==}
|
||||||
|
engines: {node: '>=14.21.3'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
|
'@biomejs/cli-win32-arm64@2.4.16':
|
||||||
|
resolution: {integrity: sha512-0rgImMsNb5v/chhkIFe3wu7PEFClS6RBAYUijGL9UsYN3PanSaoK24HSSuSJb1pYbYYVjzAyZTl3gtjJ84BM8A==}
|
||||||
|
engines: {node: '>=14.21.3'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@biomejs/cli-win32-x64@2.4.16':
|
||||||
|
resolution: {integrity: sha512-Kp85jgoBHa05gix6UIRjfCDiUV3w/8VIdZ247VyyO2gEjaw12WEVhdIjlxp/AMzXxqxQwbxNTDVZ3Mwd2RG5rw==}
|
||||||
|
engines: {node: '>=14.21.3'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@bramus/specificity@2.4.2':
|
||||||
|
resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
'@csstools/color-helpers@6.0.2':
|
||||||
|
resolution: {integrity: sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==}
|
||||||
|
engines: {node: '>=20.19.0'}
|
||||||
|
|
||||||
|
'@csstools/css-calc@3.2.1':
|
||||||
|
resolution: {integrity: sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==}
|
||||||
|
engines: {node: '>=20.19.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@csstools/css-parser-algorithms': ^4.0.0
|
||||||
|
'@csstools/css-tokenizer': ^4.0.0
|
||||||
|
|
||||||
|
'@csstools/css-color-parser@4.1.1':
|
||||||
|
resolution: {integrity: sha512-eZ5XOtyhK+mggRafYUWzA0tvaYOFgdY8AkgQiCJF9qNAePnUo/zmsqqYubBBb3sQ8uNUaSKTY9s9klfRaAXL0g==}
|
||||||
|
engines: {node: '>=20.19.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@csstools/css-parser-algorithms': ^4.0.0
|
||||||
|
'@csstools/css-tokenizer': ^4.0.0
|
||||||
|
|
||||||
|
'@csstools/css-parser-algorithms@4.0.0':
|
||||||
|
resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==}
|
||||||
|
engines: {node: '>=20.19.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@csstools/css-tokenizer': ^4.0.0
|
||||||
|
|
||||||
|
'@csstools/css-syntax-patches-for-csstree@1.1.4':
|
||||||
|
resolution: {integrity: sha512-wgsqt92b7C7tQhIdPNxj0n9zuUbQlvAuI1exyzeNrOKOi62SD7ren8zqszmpVREjAOqg8cD2FqYhQfAuKjk4sw==}
|
||||||
|
peerDependencies:
|
||||||
|
css-tree: ^3.2.1
|
||||||
|
peerDependenciesMeta:
|
||||||
|
css-tree:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@csstools/css-tokenizer@4.0.0':
|
||||||
|
resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==}
|
||||||
|
engines: {node: '>=20.19.0'}
|
||||||
|
|
||||||
'@emnapi/core@1.10.0':
|
'@emnapi/core@1.10.0':
|
||||||
resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==}
|
resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==}
|
||||||
|
|
||||||
@@ -124,6 +268,15 @@ packages:
|
|||||||
'@emnapi/wasi-threads@1.2.1':
|
'@emnapi/wasi-threads@1.2.1':
|
||||||
resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==}
|
resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==}
|
||||||
|
|
||||||
|
'@exodus/bytes@1.15.1':
|
||||||
|
resolution: {integrity: sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==}
|
||||||
|
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
|
||||||
|
peerDependencies:
|
||||||
|
'@noble/hashes': ^1.8.0 || ^2.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@noble/hashes':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@floating-ui/core@1.7.5':
|
'@floating-ui/core@1.7.5':
|
||||||
resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==}
|
resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==}
|
||||||
|
|
||||||
@@ -731,6 +884,15 @@ packages:
|
|||||||
babel-plugin-react-compiler:
|
babel-plugin-react-compiler:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@vitest/coverage-v8@4.1.6':
|
||||||
|
resolution: {integrity: sha512-36l628fQ/9a/8ihy97eOtEnvWQEdqULQOJtcaxtoNq0G1w3Mxd4szSahOaMM9/NGyZ+hyKcMtIW/WIxq0XQViQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@vitest/browser': 4.1.6
|
||||||
|
vitest: 4.1.6
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@vitest/browser':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@vitest/expect@4.1.6':
|
'@vitest/expect@4.1.6':
|
||||||
resolution: {integrity: sha512-7EHDquPthALSV0jhhjgEW8FXaviMx7rSqu8W6oqCoAuOhKov814P99QDV1pxMA3QPv21YudvJngIhjrNI4opLg==}
|
resolution: {integrity: sha512-7EHDquPthALSV0jhhjgEW8FXaviMx7rSqu8W6oqCoAuOhKov814P99QDV1pxMA3QPv21YudvJngIhjrNI4opLg==}
|
||||||
|
|
||||||
@@ -764,6 +926,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
|
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
|
||||||
engines: {node: '>= 6.0.0'}
|
engines: {node: '>= 6.0.0'}
|
||||||
|
|
||||||
|
agent-base@7.1.4:
|
||||||
|
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
|
||||||
|
engines: {node: '>= 14'}
|
||||||
|
|
||||||
any-promise@1.3.0:
|
any-promise@1.3.0:
|
||||||
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
|
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
|
||||||
|
|
||||||
@@ -782,6 +948,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
|
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
ast-v8-to-istanbul@1.0.2:
|
||||||
|
resolution: {integrity: sha512-dKmJxJsGItLmc5CYZKuEjuG6GnBs6PG4gohMhyFOWKaNQoYCuRZJDECaBlHmcG0lv2wc2E0uU8lESmBEumC3DQ==}
|
||||||
|
|
||||||
asynckit@0.4.0:
|
asynckit@0.4.0:
|
||||||
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||||
|
|
||||||
@@ -800,6 +969,9 @@ packages:
|
|||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
bidi-js@1.0.3:
|
||||||
|
resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==}
|
||||||
|
|
||||||
binary-extensions@2.3.0:
|
binary-extensions@2.3.0:
|
||||||
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
|
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -854,14 +1026,26 @@ packages:
|
|||||||
resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==}
|
resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
css-tree@3.2.1:
|
||||||
|
resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==}
|
||||||
|
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
|
||||||
|
|
||||||
cssesc@3.0.0:
|
cssesc@3.0.0:
|
||||||
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
|
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
cssstyle@6.2.0:
|
||||||
|
resolution: {integrity: sha512-Fm5NvhYathRnXNVndkUsCCuR63DCLVVwGOOwQw782coXFi5HhkXdu289l59HlXZBawsyNccXfWRYvLzcDCdDig==}
|
||||||
|
engines: {node: '>=20'}
|
||||||
|
|
||||||
csstype@3.2.3:
|
csstype@3.2.3:
|
||||||
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
|
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
|
||||||
|
|
||||||
|
data-urls@7.0.0:
|
||||||
|
resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==}
|
||||||
|
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
|
||||||
|
|
||||||
debug@4.4.3:
|
debug@4.4.3:
|
||||||
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
||||||
engines: {node: '>=6.0'}
|
engines: {node: '>=6.0'}
|
||||||
@@ -871,6 +1055,9 @@ packages:
|
|||||||
supports-color:
|
supports-color:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
decimal.js@10.6.0:
|
||||||
|
resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
|
||||||
|
|
||||||
delayed-stream@1.0.0:
|
delayed-stream@1.0.0:
|
||||||
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
@@ -895,6 +1082,10 @@ packages:
|
|||||||
electron-to-chromium@1.5.355:
|
electron-to-chromium@1.5.355:
|
||||||
resolution: {integrity: sha512-LUPZhKzZPYSPme1jEYohpkA+ybYCJztr1quAdBd7E7h3+VOBVcKkwwtBJu41nrjawrRzfb8mtMfzWozoaK0ZIQ==}
|
resolution: {integrity: sha512-LUPZhKzZPYSPme1jEYohpkA+ybYCJztr1quAdBd7E7h3+VOBVcKkwwtBJu41nrjawrRzfb8mtMfzWozoaK0ZIQ==}
|
||||||
|
|
||||||
|
entities@8.0.0:
|
||||||
|
resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==}
|
||||||
|
engines: {node: '>=20.19.0'}
|
||||||
|
|
||||||
es-define-property@1.0.1:
|
es-define-property@1.0.1:
|
||||||
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
|
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -998,6 +1189,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
has-flag@4.0.0:
|
||||||
|
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
has-symbols@1.1.0:
|
has-symbols@1.1.0:
|
||||||
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
|
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -1010,10 +1205,25 @@ packages:
|
|||||||
resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==}
|
resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
html-encoding-sniffer@6.0.0:
|
||||||
|
resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==}
|
||||||
|
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
|
||||||
|
|
||||||
|
html-escaper@2.0.2:
|
||||||
|
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
|
||||||
|
|
||||||
|
http-proxy-agent@7.0.2:
|
||||||
|
resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
|
||||||
|
engines: {node: '>= 14'}
|
||||||
|
|
||||||
https-proxy-agent@5.0.1:
|
https-proxy-agent@5.0.1:
|
||||||
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
|
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
|
https-proxy-agent@7.0.6:
|
||||||
|
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
|
||||||
|
engines: {node: '>= 14'}
|
||||||
|
|
||||||
is-binary-path@2.1.0:
|
is-binary-path@2.1.0:
|
||||||
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
|
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -1034,10 +1244,37 @@ packages:
|
|||||||
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
||||||
engines: {node: '>=0.12.0'}
|
engines: {node: '>=0.12.0'}
|
||||||
|
|
||||||
|
is-potential-custom-element-name@1.0.1:
|
||||||
|
resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
|
||||||
|
|
||||||
|
istanbul-lib-coverage@3.2.2:
|
||||||
|
resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
istanbul-lib-report@3.0.1:
|
||||||
|
resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
istanbul-reports@3.2.0:
|
||||||
|
resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
jiti@1.21.7:
|
jiti@1.21.7:
|
||||||
resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
|
resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
js-tokens@10.0.0:
|
||||||
|
resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==}
|
||||||
|
|
||||||
|
jsdom@28.1.0:
|
||||||
|
resolution: {integrity: sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==}
|
||||||
|
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
|
||||||
|
peerDependencies:
|
||||||
|
canvas: ^3.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
canvas:
|
||||||
|
optional: true
|
||||||
|
|
||||||
jwt-decode@4.0.0:
|
jwt-decode@4.0.0:
|
||||||
resolution: {integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==}
|
resolution: {integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@@ -1123,6 +1360,10 @@ packages:
|
|||||||
lines-and-columns@1.2.4:
|
lines-and-columns@1.2.4:
|
||||||
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
|
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
|
||||||
|
|
||||||
|
lru-cache@11.5.1:
|
||||||
|
resolution: {integrity: sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==}
|
||||||
|
engines: {node: 20 || >=22}
|
||||||
|
|
||||||
lucide-react@1.14.0:
|
lucide-react@1.14.0:
|
||||||
resolution: {integrity: sha512-+1mdWcfSJVUsaTIjN9zoezmUhfXo5l0vP7ekBMPo3jcS/aIkxHnXqAPsByszMZx/Y8oQBRJxJx5xg+RH3urzxA==}
|
resolution: {integrity: sha512-+1mdWcfSJVUsaTIjN9zoezmUhfXo5l0vP7ekBMPo3jcS/aIkxHnXqAPsByszMZx/Y8oQBRJxJx5xg+RH3urzxA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1131,10 +1372,20 @@ packages:
|
|||||||
magic-string@0.30.21:
|
magic-string@0.30.21:
|
||||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||||
|
|
||||||
|
magicast@0.5.3:
|
||||||
|
resolution: {integrity: sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==}
|
||||||
|
|
||||||
|
make-dir@4.0.0:
|
||||||
|
resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
math-intrinsics@1.1.0:
|
math-intrinsics@1.1.0:
|
||||||
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
mdn-data@2.27.1:
|
||||||
|
resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==}
|
||||||
|
|
||||||
merge2@1.4.1:
|
merge2@1.4.1:
|
||||||
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
@@ -1184,6 +1435,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-l2q8l9CTCTOlbX+AnK4p3M+4CEpKpyQhle6blQkdFhm0IsBqsxm15bYaSa11G7pWdsYr6epdsRZxJpCyCRbT8A==}
|
resolution: {integrity: sha512-l2q8l9CTCTOlbX+AnK4p3M+4CEpKpyQhle6blQkdFhm0IsBqsxm15bYaSa11G7pWdsYr6epdsRZxJpCyCRbT8A==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
parse5@8.0.1:
|
||||||
|
resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==}
|
||||||
|
|
||||||
path-parse@1.0.7:
|
path-parse@1.0.7:
|
||||||
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
|
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
|
||||||
|
|
||||||
@@ -1270,6 +1524,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==}
|
resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
punycode@2.3.1:
|
||||||
|
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
queue-microtask@1.2.3:
|
queue-microtask@1.2.3:
|
||||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||||
|
|
||||||
@@ -1349,6 +1607,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
||||||
engines: {node: '>=8.10.0'}
|
engines: {node: '>=8.10.0'}
|
||||||
|
|
||||||
|
require-from-string@2.0.2:
|
||||||
|
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
resolve@1.22.12:
|
resolve@1.22.12:
|
||||||
resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==}
|
resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -1366,9 +1628,18 @@ packages:
|
|||||||
run-parallel@1.2.0:
|
run-parallel@1.2.0:
|
||||||
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
|
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
|
||||||
|
|
||||||
|
saxes@6.0.0:
|
||||||
|
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
|
||||||
|
engines: {node: '>=v12.22.7'}
|
||||||
|
|
||||||
scheduler@0.27.0:
|
scheduler@0.27.0:
|
||||||
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
|
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
|
||||||
|
|
||||||
|
semver@7.8.1:
|
||||||
|
resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
set-cookie-parser@2.7.2:
|
set-cookie-parser@2.7.2:
|
||||||
resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==}
|
resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==}
|
||||||
|
|
||||||
@@ -1390,10 +1661,17 @@ packages:
|
|||||||
engines: {node: '>=16 || 14 >=14.17'}
|
engines: {node: '>=16 || 14 >=14.17'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
supports-color@7.2.0:
|
||||||
|
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
supports-preserve-symlinks-flag@1.0.0:
|
supports-preserve-symlinks-flag@1.0.0:
|
||||||
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
|
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
symbol-tree@3.2.4:
|
||||||
|
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
|
||||||
|
|
||||||
tailwind-merge@3.6.0:
|
tailwind-merge@3.6.0:
|
||||||
resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==}
|
resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==}
|
||||||
|
|
||||||
@@ -1429,10 +1707,25 @@ packages:
|
|||||||
resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==}
|
resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
|
|
||||||
|
tldts-core@7.4.0:
|
||||||
|
resolution: {integrity: sha512-/mb9kRld+x1sIMXxWNOAp5m6C+D4GrAORWlJkOJ5dElvxdN1eutz/o7qHLp9gFvDF4Y3/L2xeScoxz6AbEo8rQ==}
|
||||||
|
|
||||||
|
tldts@7.4.0:
|
||||||
|
resolution: {integrity: sha512-yHBe+zVfzNZ3QfTPW/Z6KK1G2t340gFjMHqI/4KKSt/abzYydzuCnpqdaF5gCCABby+9Yfbj59oR5F2Fd5CBzg==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
to-regex-range@5.0.1:
|
to-regex-range@5.0.1:
|
||||||
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
||||||
engines: {node: '>=8.0'}
|
engines: {node: '>=8.0'}
|
||||||
|
|
||||||
|
tough-cookie@6.0.1:
|
||||||
|
resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==}
|
||||||
|
engines: {node: '>=16'}
|
||||||
|
|
||||||
|
tr46@6.0.0:
|
||||||
|
resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==}
|
||||||
|
engines: {node: '>=20'}
|
||||||
|
|
||||||
ts-interface-checker@0.1.13:
|
ts-interface-checker@0.1.13:
|
||||||
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
|
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
|
||||||
|
|
||||||
@@ -1447,6 +1740,10 @@ packages:
|
|||||||
undici-types@7.21.0:
|
undici-types@7.21.0:
|
||||||
resolution: {integrity: sha512-w9IMgQrz4O0YN1LtB7K5P63vhlIOvC7opSmouCJ+ZywlPAlO9gIkJ+otk6LvGpAs2wg4econaCz3TvQ9xPoyuQ==}
|
resolution: {integrity: sha512-w9IMgQrz4O0YN1LtB7K5P63vhlIOvC7opSmouCJ+ZywlPAlO9gIkJ+otk6LvGpAs2wg4econaCz3TvQ9xPoyuQ==}
|
||||||
|
|
||||||
|
undici@7.26.0:
|
||||||
|
resolution: {integrity: sha512-3O9Tf67pGhgOv9jM35AbhkXAKi13f3oy3aE4CSgr+TckGeY+/iu97ZXN+J7DpHPzLbVApFd1IFhcnBjREYXYcg==}
|
||||||
|
engines: {node: '>=20.18.1'}
|
||||||
|
|
||||||
update-browserslist-db@1.2.3:
|
update-browserslist-db@1.2.3:
|
||||||
resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==}
|
resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -1565,18 +1862,141 @@ packages:
|
|||||||
jsdom:
|
jsdom:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
w3c-xmlserializer@5.0.0:
|
||||||
|
resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
webidl-conversions@8.0.1:
|
||||||
|
resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==}
|
||||||
|
engines: {node: '>=20'}
|
||||||
|
|
||||||
|
whatwg-mimetype@5.0.0:
|
||||||
|
resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==}
|
||||||
|
engines: {node: '>=20'}
|
||||||
|
|
||||||
|
whatwg-url@16.0.1:
|
||||||
|
resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==}
|
||||||
|
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
|
||||||
|
|
||||||
why-is-node-running@2.3.0:
|
why-is-node-running@2.3.0:
|
||||||
resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
|
resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
xml-name-validator@5.0.0:
|
||||||
|
resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
xmlchars@2.2.0:
|
||||||
|
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
|
||||||
|
|
||||||
zod@4.4.3:
|
zod@4.4.3:
|
||||||
resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==}
|
resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==}
|
||||||
|
|
||||||
snapshots:
|
snapshots:
|
||||||
|
|
||||||
|
'@acemir/cssom@0.9.31': {}
|
||||||
|
|
||||||
'@alloc/quick-lru@5.2.0': {}
|
'@alloc/quick-lru@5.2.0': {}
|
||||||
|
|
||||||
|
'@asamuzakjp/css-color@5.1.11':
|
||||||
|
dependencies:
|
||||||
|
'@asamuzakjp/generational-cache': 1.0.1
|
||||||
|
'@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
|
||||||
|
'@csstools/css-color-parser': 4.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
|
||||||
|
'@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
|
||||||
|
'@csstools/css-tokenizer': 4.0.0
|
||||||
|
|
||||||
|
'@asamuzakjp/dom-selector@6.8.1':
|
||||||
|
dependencies:
|
||||||
|
'@asamuzakjp/nwsapi': 2.3.9
|
||||||
|
bidi-js: 1.0.3
|
||||||
|
css-tree: 3.2.1
|
||||||
|
is-potential-custom-element-name: 1.0.1
|
||||||
|
lru-cache: 11.5.1
|
||||||
|
|
||||||
|
'@asamuzakjp/generational-cache@1.0.1': {}
|
||||||
|
|
||||||
|
'@asamuzakjp/nwsapi@2.3.9': {}
|
||||||
|
|
||||||
|
'@babel/helper-string-parser@7.29.7': {}
|
||||||
|
|
||||||
|
'@babel/helper-validator-identifier@7.29.7': {}
|
||||||
|
|
||||||
|
'@babel/parser@7.29.7':
|
||||||
|
dependencies:
|
||||||
|
'@babel/types': 7.29.7
|
||||||
|
|
||||||
|
'@babel/types@7.29.7':
|
||||||
|
dependencies:
|
||||||
|
'@babel/helper-string-parser': 7.29.7
|
||||||
|
'@babel/helper-validator-identifier': 7.29.7
|
||||||
|
|
||||||
|
'@bcoe/v8-coverage@1.0.2': {}
|
||||||
|
|
||||||
|
'@biomejs/biome@2.4.16':
|
||||||
|
optionalDependencies:
|
||||||
|
'@biomejs/cli-darwin-arm64': 2.4.16
|
||||||
|
'@biomejs/cli-darwin-x64': 2.4.16
|
||||||
|
'@biomejs/cli-linux-arm64': 2.4.16
|
||||||
|
'@biomejs/cli-linux-arm64-musl': 2.4.16
|
||||||
|
'@biomejs/cli-linux-x64': 2.4.16
|
||||||
|
'@biomejs/cli-linux-x64-musl': 2.4.16
|
||||||
|
'@biomejs/cli-win32-arm64': 2.4.16
|
||||||
|
'@biomejs/cli-win32-x64': 2.4.16
|
||||||
|
|
||||||
|
'@biomejs/cli-darwin-arm64@2.4.16':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@biomejs/cli-darwin-x64@2.4.16':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@biomejs/cli-linux-arm64-musl@2.4.16':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@biomejs/cli-linux-arm64@2.4.16':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@biomejs/cli-linux-x64-musl@2.4.16':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@biomejs/cli-linux-x64@2.4.16':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@biomejs/cli-win32-arm64@2.4.16':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@biomejs/cli-win32-x64@2.4.16':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@bramus/specificity@2.4.2':
|
||||||
|
dependencies:
|
||||||
|
css-tree: 3.2.1
|
||||||
|
|
||||||
|
'@csstools/color-helpers@6.0.2': {}
|
||||||
|
|
||||||
|
'@csstools/css-calc@3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)':
|
||||||
|
dependencies:
|
||||||
|
'@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
|
||||||
|
'@csstools/css-tokenizer': 4.0.0
|
||||||
|
|
||||||
|
'@csstools/css-color-parser@4.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)':
|
||||||
|
dependencies:
|
||||||
|
'@csstools/color-helpers': 6.0.2
|
||||||
|
'@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
|
||||||
|
'@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
|
||||||
|
'@csstools/css-tokenizer': 4.0.0
|
||||||
|
|
||||||
|
'@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)':
|
||||||
|
dependencies:
|
||||||
|
'@csstools/css-tokenizer': 4.0.0
|
||||||
|
|
||||||
|
'@csstools/css-syntax-patches-for-csstree@1.1.4(css-tree@3.2.1)':
|
||||||
|
optionalDependencies:
|
||||||
|
css-tree: 3.2.1
|
||||||
|
|
||||||
|
'@csstools/css-tokenizer@4.0.0': {}
|
||||||
|
|
||||||
'@emnapi/core@1.10.0':
|
'@emnapi/core@1.10.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@emnapi/wasi-threads': 1.2.1
|
'@emnapi/wasi-threads': 1.2.1
|
||||||
@@ -1593,6 +2013,8 @@ snapshots:
|
|||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@exodus/bytes@1.15.1': {}
|
||||||
|
|
||||||
'@floating-ui/core@1.7.5':
|
'@floating-ui/core@1.7.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@floating-ui/utils': 0.2.11
|
'@floating-ui/utils': 0.2.11
|
||||||
@@ -2132,6 +2554,20 @@ snapshots:
|
|||||||
'@rolldown/pluginutils': 1.0.0-rc.7
|
'@rolldown/pluginutils': 1.0.0-rc.7
|
||||||
vite: 8.0.13(@types/node@25.7.0)(jiti@1.21.7)
|
vite: 8.0.13(@types/node@25.7.0)(jiti@1.21.7)
|
||||||
|
|
||||||
|
'@vitest/coverage-v8@4.1.6(vitest@4.1.6)':
|
||||||
|
dependencies:
|
||||||
|
'@bcoe/v8-coverage': 1.0.2
|
||||||
|
'@vitest/utils': 4.1.6
|
||||||
|
ast-v8-to-istanbul: 1.0.2
|
||||||
|
istanbul-lib-coverage: 3.2.2
|
||||||
|
istanbul-lib-report: 3.0.1
|
||||||
|
istanbul-reports: 3.2.0
|
||||||
|
magicast: 0.5.3
|
||||||
|
obug: 2.1.1
|
||||||
|
std-env: 4.1.0
|
||||||
|
tinyrainbow: 3.1.0
|
||||||
|
vitest: 4.1.6(@types/node@25.7.0)(@vitest/coverage-v8@4.1.6)(jsdom@28.1.0)(vite@8.0.13(@types/node@25.7.0)(jiti@1.21.7))
|
||||||
|
|
||||||
'@vitest/expect@4.1.6':
|
'@vitest/expect@4.1.6':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@standard-schema/spec': 1.1.0
|
'@standard-schema/spec': 1.1.0
|
||||||
@@ -2179,6 +2615,8 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
agent-base@7.1.4: {}
|
||||||
|
|
||||||
any-promise@1.3.0: {}
|
any-promise@1.3.0: {}
|
||||||
|
|
||||||
anymatch@3.1.3:
|
anymatch@3.1.3:
|
||||||
@@ -2194,6 +2632,12 @@ snapshots:
|
|||||||
|
|
||||||
assertion-error@2.0.1: {}
|
assertion-error@2.0.1: {}
|
||||||
|
|
||||||
|
ast-v8-to-istanbul@1.0.2:
|
||||||
|
dependencies:
|
||||||
|
'@jridgewell/trace-mapping': 0.3.31
|
||||||
|
estree-walker: 3.0.3
|
||||||
|
js-tokens: 10.0.0
|
||||||
|
|
||||||
asynckit@0.4.0: {}
|
asynckit@0.4.0: {}
|
||||||
|
|
||||||
autoprefixer@10.5.0(postcss@8.5.14):
|
autoprefixer@10.5.0(postcss@8.5.14):
|
||||||
@@ -2217,6 +2661,10 @@ snapshots:
|
|||||||
|
|
||||||
baseline-browser-mapping@2.10.29: {}
|
baseline-browser-mapping@2.10.29: {}
|
||||||
|
|
||||||
|
bidi-js@1.0.3:
|
||||||
|
dependencies:
|
||||||
|
require-from-string: 2.0.2
|
||||||
|
|
||||||
binary-extensions@2.3.0: {}
|
binary-extensions@2.3.0: {}
|
||||||
|
|
||||||
braces@3.0.3:
|
braces@3.0.3:
|
||||||
@@ -2270,14 +2718,35 @@ snapshots:
|
|||||||
|
|
||||||
cookie@1.1.1: {}
|
cookie@1.1.1: {}
|
||||||
|
|
||||||
|
css-tree@3.2.1:
|
||||||
|
dependencies:
|
||||||
|
mdn-data: 2.27.1
|
||||||
|
source-map-js: 1.2.1
|
||||||
|
|
||||||
cssesc@3.0.0: {}
|
cssesc@3.0.0: {}
|
||||||
|
|
||||||
|
cssstyle@6.2.0:
|
||||||
|
dependencies:
|
||||||
|
'@asamuzakjp/css-color': 5.1.11
|
||||||
|
'@csstools/css-syntax-patches-for-csstree': 1.1.4(css-tree@3.2.1)
|
||||||
|
css-tree: 3.2.1
|
||||||
|
lru-cache: 11.5.1
|
||||||
|
|
||||||
csstype@3.2.3: {}
|
csstype@3.2.3: {}
|
||||||
|
|
||||||
|
data-urls@7.0.0:
|
||||||
|
dependencies:
|
||||||
|
whatwg-mimetype: 5.0.0
|
||||||
|
whatwg-url: 16.0.1
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@noble/hashes'
|
||||||
|
|
||||||
debug@4.4.3:
|
debug@4.4.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.1.3
|
ms: 2.1.3
|
||||||
|
|
||||||
|
decimal.js@10.6.0: {}
|
||||||
|
|
||||||
delayed-stream@1.0.0: {}
|
delayed-stream@1.0.0: {}
|
||||||
|
|
||||||
detect-libc@2.1.2: {}
|
detect-libc@2.1.2: {}
|
||||||
@@ -2296,6 +2765,8 @@ snapshots:
|
|||||||
|
|
||||||
electron-to-chromium@1.5.355: {}
|
electron-to-chromium@1.5.355: {}
|
||||||
|
|
||||||
|
entities@8.0.0: {}
|
||||||
|
|
||||||
es-define-property@1.0.1: {}
|
es-define-property@1.0.1: {}
|
||||||
|
|
||||||
es-errors@1.3.0: {}
|
es-errors@1.3.0: {}
|
||||||
@@ -2391,6 +2862,8 @@ snapshots:
|
|||||||
|
|
||||||
gopd@1.2.0: {}
|
gopd@1.2.0: {}
|
||||||
|
|
||||||
|
has-flag@4.0.0: {}
|
||||||
|
|
||||||
has-symbols@1.1.0: {}
|
has-symbols@1.1.0: {}
|
||||||
|
|
||||||
has-tostringtag@1.0.2:
|
has-tostringtag@1.0.2:
|
||||||
@@ -2401,6 +2874,21 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
function-bind: 1.1.2
|
function-bind: 1.1.2
|
||||||
|
|
||||||
|
html-encoding-sniffer@6.0.0:
|
||||||
|
dependencies:
|
||||||
|
'@exodus/bytes': 1.15.1
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@noble/hashes'
|
||||||
|
|
||||||
|
html-escaper@2.0.2: {}
|
||||||
|
|
||||||
|
http-proxy-agent@7.0.2:
|
||||||
|
dependencies:
|
||||||
|
agent-base: 7.1.4
|
||||||
|
debug: 4.4.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
https-proxy-agent@5.0.1:
|
https-proxy-agent@5.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base: 6.0.2
|
agent-base: 6.0.2
|
||||||
@@ -2408,6 +2896,13 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
https-proxy-agent@7.0.6:
|
||||||
|
dependencies:
|
||||||
|
agent-base: 7.1.4
|
||||||
|
debug: 4.4.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
is-binary-path@2.1.0:
|
is-binary-path@2.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
binary-extensions: 2.3.0
|
binary-extensions: 2.3.0
|
||||||
@@ -2424,8 +2919,52 @@ snapshots:
|
|||||||
|
|
||||||
is-number@7.0.0: {}
|
is-number@7.0.0: {}
|
||||||
|
|
||||||
|
is-potential-custom-element-name@1.0.1: {}
|
||||||
|
|
||||||
|
istanbul-lib-coverage@3.2.2: {}
|
||||||
|
|
||||||
|
istanbul-lib-report@3.0.1:
|
||||||
|
dependencies:
|
||||||
|
istanbul-lib-coverage: 3.2.2
|
||||||
|
make-dir: 4.0.0
|
||||||
|
supports-color: 7.2.0
|
||||||
|
|
||||||
|
istanbul-reports@3.2.0:
|
||||||
|
dependencies:
|
||||||
|
html-escaper: 2.0.2
|
||||||
|
istanbul-lib-report: 3.0.1
|
||||||
|
|
||||||
jiti@1.21.7: {}
|
jiti@1.21.7: {}
|
||||||
|
|
||||||
|
js-tokens@10.0.0: {}
|
||||||
|
|
||||||
|
jsdom@28.1.0:
|
||||||
|
dependencies:
|
||||||
|
'@acemir/cssom': 0.9.31
|
||||||
|
'@asamuzakjp/dom-selector': 6.8.1
|
||||||
|
'@bramus/specificity': 2.4.2
|
||||||
|
'@exodus/bytes': 1.15.1
|
||||||
|
cssstyle: 6.2.0
|
||||||
|
data-urls: 7.0.0
|
||||||
|
decimal.js: 10.6.0
|
||||||
|
html-encoding-sniffer: 6.0.0
|
||||||
|
http-proxy-agent: 7.0.2
|
||||||
|
https-proxy-agent: 7.0.6
|
||||||
|
is-potential-custom-element-name: 1.0.1
|
||||||
|
parse5: 8.0.1
|
||||||
|
saxes: 6.0.0
|
||||||
|
symbol-tree: 3.2.4
|
||||||
|
tough-cookie: 6.0.1
|
||||||
|
undici: 7.26.0
|
||||||
|
w3c-xmlserializer: 5.0.0
|
||||||
|
webidl-conversions: 8.0.1
|
||||||
|
whatwg-mimetype: 5.0.0
|
||||||
|
whatwg-url: 16.0.1
|
||||||
|
xml-name-validator: 5.0.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@noble/hashes'
|
||||||
|
- supports-color
|
||||||
|
|
||||||
jwt-decode@4.0.0: {}
|
jwt-decode@4.0.0: {}
|
||||||
|
|
||||||
lightningcss-android-arm64@1.32.0:
|
lightningcss-android-arm64@1.32.0:
|
||||||
@@ -2481,6 +3020,8 @@ snapshots:
|
|||||||
|
|
||||||
lines-and-columns@1.2.4: {}
|
lines-and-columns@1.2.4: {}
|
||||||
|
|
||||||
|
lru-cache@11.5.1: {}
|
||||||
|
|
||||||
lucide-react@1.14.0(react@19.2.6):
|
lucide-react@1.14.0(react@19.2.6):
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 19.2.6
|
react: 19.2.6
|
||||||
@@ -2489,8 +3030,20 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/sourcemap-codec': 1.5.5
|
'@jridgewell/sourcemap-codec': 1.5.5
|
||||||
|
|
||||||
|
magicast@0.5.3:
|
||||||
|
dependencies:
|
||||||
|
'@babel/parser': 7.29.7
|
||||||
|
'@babel/types': 7.29.7
|
||||||
|
source-map-js: 1.2.1
|
||||||
|
|
||||||
|
make-dir@4.0.0:
|
||||||
|
dependencies:
|
||||||
|
semver: 7.8.1
|
||||||
|
|
||||||
math-intrinsics@1.1.0: {}
|
math-intrinsics@1.1.0: {}
|
||||||
|
|
||||||
|
mdn-data@2.27.1: {}
|
||||||
|
|
||||||
merge2@1.4.1: {}
|
merge2@1.4.1: {}
|
||||||
|
|
||||||
micromatch@4.0.8:
|
micromatch@4.0.8:
|
||||||
@@ -2528,6 +3081,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
jwt-decode: 4.0.0
|
jwt-decode: 4.0.0
|
||||||
|
|
||||||
|
parse5@8.0.1:
|
||||||
|
dependencies:
|
||||||
|
entities: 8.0.0
|
||||||
|
|
||||||
path-parse@1.0.7: {}
|
path-parse@1.0.7: {}
|
||||||
|
|
||||||
pathe@2.0.3: {}
|
pathe@2.0.3: {}
|
||||||
@@ -2589,6 +3146,8 @@ snapshots:
|
|||||||
|
|
||||||
proxy-from-env@2.1.0: {}
|
proxy-from-env@2.1.0: {}
|
||||||
|
|
||||||
|
punycode@2.3.1: {}
|
||||||
|
|
||||||
queue-microtask@1.2.3: {}
|
queue-microtask@1.2.3: {}
|
||||||
|
|
||||||
react-dom@19.2.6(react@19.2.6):
|
react-dom@19.2.6(react@19.2.6):
|
||||||
@@ -2656,6 +3215,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
picomatch: 2.3.2
|
picomatch: 2.3.2
|
||||||
|
|
||||||
|
require-from-string@2.0.2: {}
|
||||||
|
|
||||||
resolve@1.22.12:
|
resolve@1.22.12:
|
||||||
dependencies:
|
dependencies:
|
||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
@@ -2690,8 +3251,14 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
queue-microtask: 1.2.3
|
queue-microtask: 1.2.3
|
||||||
|
|
||||||
|
saxes@6.0.0:
|
||||||
|
dependencies:
|
||||||
|
xmlchars: 2.2.0
|
||||||
|
|
||||||
scheduler@0.27.0: {}
|
scheduler@0.27.0: {}
|
||||||
|
|
||||||
|
semver@7.8.1: {}
|
||||||
|
|
||||||
set-cookie-parser@2.7.2: {}
|
set-cookie-parser@2.7.2: {}
|
||||||
|
|
||||||
siginfo@2.0.0: {}
|
siginfo@2.0.0: {}
|
||||||
@@ -2712,8 +3279,14 @@ snapshots:
|
|||||||
tinyglobby: 0.2.16
|
tinyglobby: 0.2.16
|
||||||
ts-interface-checker: 0.1.13
|
ts-interface-checker: 0.1.13
|
||||||
|
|
||||||
|
supports-color@7.2.0:
|
||||||
|
dependencies:
|
||||||
|
has-flag: 4.0.0
|
||||||
|
|
||||||
supports-preserve-symlinks-flag@1.0.0: {}
|
supports-preserve-symlinks-flag@1.0.0: {}
|
||||||
|
|
||||||
|
symbol-tree@3.2.4: {}
|
||||||
|
|
||||||
tailwind-merge@3.6.0: {}
|
tailwind-merge@3.6.0: {}
|
||||||
|
|
||||||
tailwindcss-animate@1.0.7(tailwindcss@3.4.19):
|
tailwindcss-animate@1.0.7(tailwindcss@3.4.19):
|
||||||
@@ -2767,10 +3340,24 @@ snapshots:
|
|||||||
|
|
||||||
tinyrainbow@3.1.0: {}
|
tinyrainbow@3.1.0: {}
|
||||||
|
|
||||||
|
tldts-core@7.4.0: {}
|
||||||
|
|
||||||
|
tldts@7.4.0:
|
||||||
|
dependencies:
|
||||||
|
tldts-core: 7.4.0
|
||||||
|
|
||||||
to-regex-range@5.0.1:
|
to-regex-range@5.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-number: 7.0.0
|
is-number: 7.0.0
|
||||||
|
|
||||||
|
tough-cookie@6.0.1:
|
||||||
|
dependencies:
|
||||||
|
tldts: 7.4.0
|
||||||
|
|
||||||
|
tr46@6.0.0:
|
||||||
|
dependencies:
|
||||||
|
punycode: 2.3.1
|
||||||
|
|
||||||
ts-interface-checker@0.1.13: {}
|
ts-interface-checker@0.1.13: {}
|
||||||
|
|
||||||
tslib@2.8.1: {}
|
tslib@2.8.1: {}
|
||||||
@@ -2779,6 +3366,8 @@ snapshots:
|
|||||||
|
|
||||||
undici-types@7.21.0: {}
|
undici-types@7.21.0: {}
|
||||||
|
|
||||||
|
undici@7.26.0: {}
|
||||||
|
|
||||||
update-browserslist-db@1.2.3(browserslist@4.28.2):
|
update-browserslist-db@1.2.3(browserslist@4.28.2):
|
||||||
dependencies:
|
dependencies:
|
||||||
browserslist: 4.28.2
|
browserslist: 4.28.2
|
||||||
@@ -2818,7 +3407,7 @@ snapshots:
|
|||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
jiti: 1.21.7
|
jiti: 1.21.7
|
||||||
|
|
||||||
vitest@4.1.6(@types/node@25.7.0)(vite@8.0.13(@types/node@25.7.0)(jiti@1.21.7)):
|
vitest@4.1.6(@types/node@25.7.0)(@vitest/coverage-v8@4.1.6)(jsdom@28.1.0)(vite@8.0.13(@types/node@25.7.0)(jiti@1.21.7)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vitest/expect': 4.1.6
|
'@vitest/expect': 4.1.6
|
||||||
'@vitest/mocker': 4.1.6(vite@8.0.13(@types/node@25.7.0)(jiti@1.21.7))
|
'@vitest/mocker': 4.1.6(vite@8.0.13(@types/node@25.7.0)(jiti@1.21.7))
|
||||||
@@ -2842,12 +3431,34 @@ snapshots:
|
|||||||
why-is-node-running: 2.3.0
|
why-is-node-running: 2.3.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/node': 25.7.0
|
'@types/node': 25.7.0
|
||||||
|
'@vitest/coverage-v8': 4.1.6(vitest@4.1.6)
|
||||||
|
jsdom: 28.1.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- msw
|
- msw
|
||||||
|
|
||||||
|
w3c-xmlserializer@5.0.0:
|
||||||
|
dependencies:
|
||||||
|
xml-name-validator: 5.0.0
|
||||||
|
|
||||||
|
webidl-conversions@8.0.1: {}
|
||||||
|
|
||||||
|
whatwg-mimetype@5.0.0: {}
|
||||||
|
|
||||||
|
whatwg-url@16.0.1:
|
||||||
|
dependencies:
|
||||||
|
'@exodus/bytes': 1.15.1
|
||||||
|
tr46: 6.0.0
|
||||||
|
webidl-conversions: 8.0.1
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@noble/hashes'
|
||||||
|
|
||||||
why-is-node-running@2.3.0:
|
why-is-node-running@2.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
siginfo: 2.0.0
|
siginfo: 2.0.0
|
||||||
stackback: 0.0.2
|
stackback: 0.0.2
|
||||||
|
|
||||||
|
xml-name-validator@5.0.0: {}
|
||||||
|
|
||||||
|
xmlchars@2.2.0: {}
|
||||||
|
|
||||||
zod@4.4.3: {}
|
zod@4.4.3: {}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { type RouteObject, createBrowserRouter } from "react-router-dom";
|
import { createBrowserRouter, type RouteObject } from "react-router-dom";
|
||||||
import AppLayout from "../components/layout/AppLayout";
|
import AppLayout from "../components/layout/AppLayout";
|
||||||
import AuditLogsPage from "../features/audit/AuditLogsPage";
|
import AuditLogsPage from "../features/audit/AuditLogsPage";
|
||||||
import AuthCallbackPage from "../features/auth/AuthCallbackPage";
|
import AuthCallbackPage from "../features/auth/AuthCallbackPage";
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ import { useAuth } from "react-oidc-context";
|
|||||||
import { NavLink, Outlet, useLocation, useNavigate } from "react-router-dom";
|
import { NavLink, Outlet, useLocation, useNavigate } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
AppSidebar,
|
AppSidebar,
|
||||||
type ShellSidebarNavItem,
|
|
||||||
type ShellTranslator,
|
|
||||||
applyShellTheme,
|
applyShellTheme,
|
||||||
buildShellProfileSummary,
|
buildShellProfileSummary,
|
||||||
buildShellSessionStatus,
|
buildShellSessionStatus,
|
||||||
readShellSessionExpiryEnabled,
|
readShellSessionExpiryEnabled,
|
||||||
readShellTheme,
|
readShellTheme,
|
||||||
|
type ShellSidebarNavItem,
|
||||||
|
type ShellTranslator,
|
||||||
shellLayoutClasses,
|
shellLayoutClasses,
|
||||||
writeShellSessionExpiryEnabled,
|
writeShellSessionExpiryEnabled,
|
||||||
} from "../../../../common/shell";
|
} from "../../../../common/shell";
|
||||||
@@ -156,9 +156,12 @@ function AppLayout() {
|
|||||||
window.addEventListener(LOCALE_CHANGED_EVENT, rerenderDevelopmentShell);
|
window.addEventListener(LOCALE_CHANGED_EVENT, rerenderDevelopmentShell);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener(LOCALE_CHANGED_EVENT, rerenderDevelopmentShell);
|
window.removeEventListener(
|
||||||
|
LOCALE_CHANGED_EVENT,
|
||||||
|
rerenderDevelopmentShell,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}, [isDevelopmentRuntime]);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleClickOutside = (event: MouseEvent) => {
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
@@ -271,7 +274,6 @@ function AppLayout() {
|
|||||||
auth.isAuthenticated,
|
auth.isAuthenticated,
|
||||||
auth.isLoading,
|
auth.isLoading,
|
||||||
auth.user?.expires_at,
|
auth.user?.expires_at,
|
||||||
isDevelopmentRuntime,
|
|
||||||
isSessionExpiryEnabled,
|
isSessionExpiryEnabled,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -481,7 +483,10 @@ function AppLayout() {
|
|||||||
<div className="flex items-center justify-between gap-3">
|
<div className="flex items-center justify-between gap-3">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-foreground">
|
<p className="text-sm font-medium text-foreground">
|
||||||
{t("ui.shell.session.auto_extend", "Session expiry")}
|
{t(
|
||||||
|
"ui.shell.session.auto_extend",
|
||||||
|
"Session expiry",
|
||||||
|
)}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
{isSessionExpiryEnabled ? (
|
{isSessionExpiryEnabled ? (
|
||||||
|
|||||||
@@ -44,4 +44,4 @@ const AvatarFallback = React.forwardRef<
|
|||||||
));
|
));
|
||||||
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
|
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
|
||||||
|
|
||||||
export { Avatar, AvatarImage, AvatarFallback };
|
export { Avatar, AvatarFallback, AvatarImage };
|
||||||
|
|||||||
@@ -50,9 +50,9 @@ function CardFooter({
|
|||||||
|
|
||||||
export {
|
export {
|
||||||
Card,
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardFooter,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
CardDescription,
|
|
||||||
CardContent,
|
|
||||||
CardFooter,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -92,11 +92,11 @@ TableCaption.displayName = "TableCaption";
|
|||||||
|
|
||||||
export {
|
export {
|
||||||
Table,
|
Table,
|
||||||
TableHeader,
|
|
||||||
TableBody,
|
TableBody,
|
||||||
|
TableCaption,
|
||||||
|
TableCell,
|
||||||
TableFooter,
|
TableFooter,
|
||||||
TableHead,
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
TableCell,
|
|
||||||
TableCaption,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { AlertTriangle, ExternalLink, LogIn, ShieldHalf } from "lucide-react";
|
import { AlertTriangle, ExternalLink, LogIn, ShieldHalf } from "lucide-react";
|
||||||
import { useEffect, useMemo, useRef, useState } from "react";
|
import { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useAuth } from "react-oidc-context";
|
import { useAuth } from "react-oidc-context";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||||
import { useSearchParams } from "react-router-dom";
|
|
||||||
import { Button } from "../../components/ui/button";
|
import { Button } from "../../components/ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
Upload,
|
Upload,
|
||||||
X,
|
X,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { useAuth } from "react-oidc-context";
|
import { useAuth } from "react-oidc-context";
|
||||||
import { Link, useNavigate, useParams } from "react-router-dom";
|
import { Link, useNavigate, useParams } from "react-router-dom";
|
||||||
import { PageHeader } from "../../../../common/core/components/page";
|
import { PageHeader } from "../../../../common/core/components/page";
|
||||||
@@ -32,6 +32,13 @@ import { Label } from "../../components/ui/label";
|
|||||||
import { Switch } from "../../components/ui/switch";
|
import { Switch } from "../../components/ui/switch";
|
||||||
import { Textarea } from "../../components/ui/textarea";
|
import { Textarea } from "../../components/ui/textarea";
|
||||||
import { toast } from "../../components/ui/use-toast";
|
import { toast } from "../../components/ui/use-toast";
|
||||||
|
import type {
|
||||||
|
ClientStatus,
|
||||||
|
ClientType,
|
||||||
|
ClientUpsertRequest,
|
||||||
|
MyTenantSummary,
|
||||||
|
TenantSummary,
|
||||||
|
} from "../../lib/devApi";
|
||||||
import {
|
import {
|
||||||
type ClientRelation,
|
type ClientRelation,
|
||||||
createClient,
|
createClient,
|
||||||
@@ -44,13 +51,6 @@ import {
|
|||||||
updateClient,
|
updateClient,
|
||||||
updateClientStatus,
|
updateClientStatus,
|
||||||
} from "../../lib/devApi";
|
} from "../../lib/devApi";
|
||||||
import type {
|
|
||||||
ClientStatus,
|
|
||||||
ClientType,
|
|
||||||
ClientUpsertRequest,
|
|
||||||
MyTenantSummary,
|
|
||||||
TenantSummary,
|
|
||||||
} from "../../lib/devApi";
|
|
||||||
import { t } from "../../lib/i18n";
|
import { t } from "../../lib/i18n";
|
||||||
import { resolveProfileRole } from "../../lib/role";
|
import { resolveProfileRole } from "../../lib/role";
|
||||||
import { cn } from "../../lib/utils";
|
import { cn } from "../../lib/utils";
|
||||||
@@ -408,6 +408,59 @@ function ClientGeneralPage() {
|
|||||||
]);
|
]);
|
||||||
const [idTokenClaims, setIdTokenClaims] = useState<IdTokenClaimItem[]>([]);
|
const [idTokenClaims, setIdTokenClaims] = useState<IdTokenClaimItem[]>([]);
|
||||||
|
|
||||||
|
const tenantScopeDescription = t(
|
||||||
|
"msg.dev.clients.scopes.tenant",
|
||||||
|
"소속 테넌트 정보 접근",
|
||||||
|
);
|
||||||
|
|
||||||
|
const buildTenantScope = useCallback(
|
||||||
|
(id: string): ScopeItem => ({
|
||||||
|
id,
|
||||||
|
name: "tenant",
|
||||||
|
description: tenantScopeDescription,
|
||||||
|
mandatory: true,
|
||||||
|
locked: true,
|
||||||
|
}),
|
||||||
|
[tenantScopeDescription],
|
||||||
|
);
|
||||||
|
|
||||||
|
const normalizeScopesForTenantAccess = useCallback(
|
||||||
|
(nextScopes: ScopeItem[], restricted: boolean): ScopeItem[] => {
|
||||||
|
const normalized = nextScopes.map((scope) => {
|
||||||
|
if (scope.name.trim() !== "tenant") {
|
||||||
|
return scope;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...scope,
|
||||||
|
description: scope.description || tenantScopeDescription,
|
||||||
|
mandatory: restricted,
|
||||||
|
locked: restricted,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
restricted &&
|
||||||
|
!normalized.some((scope) => scope.name.trim() === "tenant")
|
||||||
|
) {
|
||||||
|
normalized.push(buildTenantScope(`tenant-${Date.now()}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
const openidScopes = normalized.filter(
|
||||||
|
(scope) => scope.name.trim() === "openid",
|
||||||
|
);
|
||||||
|
const tenantScopes = normalized.filter(
|
||||||
|
(scope) => scope.name.trim() === "tenant",
|
||||||
|
);
|
||||||
|
const remainingScopes = normalized.filter((scope) => {
|
||||||
|
const name = scope.name.trim();
|
||||||
|
return name !== "openid" && name !== "tenant";
|
||||||
|
});
|
||||||
|
|
||||||
|
return [...openidScopes, ...tenantScopes, ...remainingScopes];
|
||||||
|
},
|
||||||
|
[buildTenantScope, tenantScopeDescription],
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
const { client } = data;
|
const { client } = data;
|
||||||
@@ -511,7 +564,7 @@ function ClientGeneralPage() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
setIdTokenClaims(readIdTokenClaimsMetadata(metadata));
|
setIdTokenClaims(readIdTokenClaimsMetadata(metadata));
|
||||||
}, [data]);
|
}, [data, normalizeScopesForTenantAccess]);
|
||||||
|
|
||||||
const securityProfile: SecurityProfile =
|
const securityProfile: SecurityProfile =
|
||||||
clientType === "pkce" ? "pkce" : "private";
|
clientType === "pkce" ? "pkce" : "private";
|
||||||
@@ -574,56 +627,6 @@ function ClientGeneralPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const tenantScopeDescription = t(
|
|
||||||
"msg.dev.clients.scopes.tenant",
|
|
||||||
"소속 테넌트 정보 접근",
|
|
||||||
);
|
|
||||||
|
|
||||||
const buildTenantScope = (id: string): ScopeItem => ({
|
|
||||||
id,
|
|
||||||
name: "tenant",
|
|
||||||
description: tenantScopeDescription,
|
|
||||||
mandatory: true,
|
|
||||||
locked: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
function normalizeScopesForTenantAccess(
|
|
||||||
nextScopes: ScopeItem[],
|
|
||||||
restricted: boolean,
|
|
||||||
): ScopeItem[] {
|
|
||||||
const normalized = nextScopes.map((scope) => {
|
|
||||||
if (scope.name.trim() !== "tenant") {
|
|
||||||
return scope;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...scope,
|
|
||||||
description: scope.description || tenantScopeDescription,
|
|
||||||
mandatory: restricted,
|
|
||||||
locked: restricted,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
if (
|
|
||||||
restricted &&
|
|
||||||
!normalized.some((scope) => scope.name.trim() === "tenant")
|
|
||||||
) {
|
|
||||||
normalized.push(buildTenantScope(`tenant-${Date.now()}`));
|
|
||||||
}
|
|
||||||
|
|
||||||
const openidScopes = normalized.filter(
|
|
||||||
(scope) => scope.name.trim() === "openid",
|
|
||||||
);
|
|
||||||
const tenantScopes = normalized.filter(
|
|
||||||
(scope) => scope.name.trim() === "tenant",
|
|
||||||
);
|
|
||||||
const remainingScopes = normalized.filter((scope) => {
|
|
||||||
const name = scope.name.trim();
|
|
||||||
return name !== "openid" && name !== "tenant";
|
|
||||||
});
|
|
||||||
|
|
||||||
return [...openidScopes, ...tenantScopes, ...remainingScopes];
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleTenantAccessToggle = (enabled: boolean) => {
|
const handleTenantAccessToggle = (enabled: boolean) => {
|
||||||
setTenantAccessRestricted(enabled);
|
setTenantAccessRestricted(enabled);
|
||||||
setIsTenantSearchOpen(enabled);
|
setIsTenantSearchOpen(enabled);
|
||||||
@@ -2307,7 +2310,7 @@ function ClientGeneralPage() {
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
{securityProfile === "private" && (
|
{securityProfile === "private" && (
|
||||||
<div
|
<fieldset
|
||||||
className="mt-4 flex items-center justify-between border-t border-primary/20 pt-4"
|
className="mt-4 flex items-center justify-between border-t border-primary/20 pt-4"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
onKeyDown={(e) => e.stopPropagation()}
|
onKeyDown={(e) => e.stopPropagation()}
|
||||||
@@ -2335,7 +2338,7 @@ function ClientGeneralPage() {
|
|||||||
onCheckedChange={handleHeadlessToggle}
|
onCheckedChange={handleHeadlessToggle}
|
||||||
disabled={isGeneralSettingsReadOnly}
|
disabled={isGeneralSettingsReadOnly}
|
||||||
/>
|
/>
|
||||||
</div>
|
</fieldset>
|
||||||
)}
|
)}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
@@ -2674,104 +2677,102 @@ function ClientGeneralPage() {
|
|||||||
</div>
|
</div>
|
||||||
{currentHeadlessJwksCache.parsedKeys?.length ? (
|
{currentHeadlessJwksCache.parsedKeys?.length ? (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{currentHeadlessJwksCache.parsedKeys.map(
|
{currentHeadlessJwksCache.parsedKeys.map((key) => {
|
||||||
(key, index) => {
|
const normalizedAlgorithm = key.alg?.trim() ?? "";
|
||||||
const normalizedAlgorithm = key.alg?.trim() ?? "";
|
const isMissingAlgorithm =
|
||||||
const isMissingAlgorithm =
|
normalizedAlgorithm === "";
|
||||||
normalizedAlgorithm === "";
|
const isUnsupportedAlgorithm =
|
||||||
const isUnsupportedAlgorithm =
|
!isMissingAlgorithm &&
|
||||||
!isMissingAlgorithm &&
|
!HEADLESS_LOGIN_ALLOWED_ALGORITHM_SET.has(
|
||||||
!HEADLESS_LOGIN_ALLOWED_ALGORITHM_SET.has(
|
normalizedAlgorithm,
|
||||||
normalizedAlgorithm,
|
);
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={`${key.kid || "key"}-${index}`}
|
key={`${key.kid ?? "missing-kid"}-${key.kty ?? ""}-${key.alg ?? ""}-${key.n ?? ""}`}
|
||||||
className={cn(
|
className={cn(
|
||||||
"rounded-xl border bg-muted/30 p-3",
|
"rounded-xl border bg-muted/30 p-3",
|
||||||
isUnsupportedAlgorithm || isMissingAlgorithm
|
isUnsupportedAlgorithm || isMissingAlgorithm
|
||||||
? "border-destructive/50 bg-destructive/5"
|
? "border-destructive/50 bg-destructive/5"
|
||||||
: "border-border",
|
: "border-border",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-4">
|
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-4">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<p className="text-[11px] font-semibold uppercase text-muted-foreground">
|
|
||||||
KID
|
|
||||||
</p>
|
|
||||||
<p className="break-all rounded-lg border border-border bg-background px-3 py-2 font-mono text-[11px]">
|
|
||||||
{key.kid || "-"}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<p className="text-[11px] font-semibold uppercase text-muted-foreground">
|
|
||||||
KTY
|
|
||||||
</p>
|
|
||||||
<p className="break-all rounded-lg border border-border bg-background px-3 py-2 font-mono text-[11px]">
|
|
||||||
{key.kty || "-"}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<p className="text-[11px] font-semibold uppercase text-muted-foreground">
|
|
||||||
USE
|
|
||||||
</p>
|
|
||||||
<p className="break-all rounded-lg border border-border bg-background px-3 py-2 font-mono text-[11px]">
|
|
||||||
{key.use || "-"}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<p className="text-[11px] font-semibold uppercase text-muted-foreground">
|
|
||||||
ALG
|
|
||||||
</p>
|
|
||||||
<p
|
|
||||||
className={cn(
|
|
||||||
"break-all rounded-lg border bg-background px-3 py-2 font-mono text-[11px]",
|
|
||||||
isUnsupportedAlgorithm ||
|
|
||||||
isMissingAlgorithm
|
|
||||||
? "border-destructive/50 text-destructive"
|
|
||||||
: "border-border",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{key.alg ||
|
|
||||||
t(
|
|
||||||
"msg.dev.clients.general.public_key.cache.missing_algorithm_badge",
|
|
||||||
"알고리즘 미선언",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
{isMissingAlgorithm && (
|
|
||||||
<p className="text-[11px] text-destructive">
|
|
||||||
{t(
|
|
||||||
"msg.dev.clients.general.public_key.cache.missing_algorithm_reason",
|
|
||||||
"이 키는 `alg`가 비어 있어서 저장할 수 없습니다.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
{isUnsupportedAlgorithm && (
|
|
||||||
<p className="text-[11px] text-destructive">
|
|
||||||
{t(
|
|
||||||
"msg.dev.clients.general.public_key.cache.unsupported_algorithm_reason",
|
|
||||||
"이 알고리즘은 Headless Login에서 지원되지 않습니다.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-3 space-y-1">
|
|
||||||
<p className="text-[11px] font-semibold uppercase text-muted-foreground">
|
<p className="text-[11px] font-semibold uppercase text-muted-foreground">
|
||||||
{t(
|
KID
|
||||||
"ui.dev.clients.general.public_key.cache.parsed_key_n",
|
</p>
|
||||||
"N",
|
<p className="break-all rounded-lg border border-border bg-background px-3 py-2 font-mono text-[11px]">
|
||||||
|
{key.kid || "-"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<p className="text-[11px] font-semibold uppercase text-muted-foreground">
|
||||||
|
KTY
|
||||||
|
</p>
|
||||||
|
<p className="break-all rounded-lg border border-border bg-background px-3 py-2 font-mono text-[11px]">
|
||||||
|
{key.kty || "-"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<p className="text-[11px] font-semibold uppercase text-muted-foreground">
|
||||||
|
USE
|
||||||
|
</p>
|
||||||
|
<p className="break-all rounded-lg border border-border bg-background px-3 py-2 font-mono text-[11px]">
|
||||||
|
{key.use || "-"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<p className="text-[11px] font-semibold uppercase text-muted-foreground">
|
||||||
|
ALG
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className={cn(
|
||||||
|
"break-all rounded-lg border bg-background px-3 py-2 font-mono text-[11px]",
|
||||||
|
isUnsupportedAlgorithm ||
|
||||||
|
isMissingAlgorithm
|
||||||
|
? "border-destructive/50 text-destructive"
|
||||||
|
: "border-border",
|
||||||
)}
|
)}
|
||||||
|
>
|
||||||
|
{key.alg ||
|
||||||
|
t(
|
||||||
|
"msg.dev.clients.general.public_key.cache.missing_algorithm_badge",
|
||||||
|
"알고리즘 미선언",
|
||||||
|
)}
|
||||||
</p>
|
</p>
|
||||||
<p className="min-h-16 break-all rounded-lg border border-border bg-background px-3 py-2 font-mono text-[11px] leading-5">
|
{isMissingAlgorithm && (
|
||||||
{key.n || "-"}
|
<p className="text-[11px] text-destructive">
|
||||||
</p>
|
{t(
|
||||||
|
"msg.dev.clients.general.public_key.cache.missing_algorithm_reason",
|
||||||
|
"이 키는 `alg`가 비어 있어서 저장할 수 없습니다.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{isUnsupportedAlgorithm && (
|
||||||
|
<p className="text-[11px] text-destructive">
|
||||||
|
{t(
|
||||||
|
"msg.dev.clients.general.public_key.cache.unsupported_algorithm_reason",
|
||||||
|
"이 알고리즘은 Headless Login에서 지원되지 않습니다.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
<div className="mt-3 space-y-1">
|
||||||
},
|
<p className="text-[11px] font-semibold uppercase text-muted-foreground">
|
||||||
)}
|
{t(
|
||||||
|
"ui.dev.clients.general.public_key.cache.parsed_key_n",
|
||||||
|
"N",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<p className="min-h-16 break-all rounded-lg border border-border bg-background px-3 py-2 font-mono text-[11px] leading-5">
|
||||||
|
{key.n || "-"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="rounded-lg border border-dashed border-border px-4 py-5 text-sm text-muted-foreground">
|
<div className="rounded-lg border border-dashed border-border px-4 py-5 text-sm text-muted-foreground">
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ import {
|
|||||||
} from "../../components/ui/table";
|
} from "../../components/ui/table";
|
||||||
import { toast } from "../../components/ui/use-toast";
|
import { toast } from "../../components/ui/use-toast";
|
||||||
import {
|
import {
|
||||||
type DevAssignableUser,
|
|
||||||
addClientRelation,
|
addClientRelation,
|
||||||
|
type DevAssignableUser,
|
||||||
fetchClient,
|
fetchClient,
|
||||||
fetchClientRelations,
|
fetchClientRelations,
|
||||||
fetchDevUsers,
|
fetchDevUsers,
|
||||||
@@ -355,7 +355,10 @@ function ClientRelationsPage() {
|
|||||||
</nav>
|
</nav>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
icon={<ShieldHalf size={20} />}
|
icon={<ShieldHalf size={20} />}
|
||||||
title={t("ui.dev.clients.relationships.title", "Client Relationships")}
|
title={t(
|
||||||
|
"ui.dev.clients.relationships.title",
|
||||||
|
"Client Relationships",
|
||||||
|
)}
|
||||||
description={t(
|
description={t(
|
||||||
"msg.dev.clients.relationships.subtitle",
|
"msg.dev.clients.relationships.subtitle",
|
||||||
"RP direct operator relation을 조회하고 User 단위로 추가·삭제합니다.",
|
"RP direct operator relation을 조회하고 User 단위로 추가·삭제합니다.",
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||||
import type { AxiosError } from "axios";
|
import type { AxiosError } from "axios";
|
||||||
import { BookOpenText, Filter, Plus, Search, ShieldHalf, X } from "lucide-react";
|
import {
|
||||||
|
BookOpenText,
|
||||||
|
Filter,
|
||||||
|
Plus,
|
||||||
|
Search,
|
||||||
|
ShieldHalf,
|
||||||
|
X,
|
||||||
|
} from "lucide-react";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { useAuth } from "react-oidc-context";
|
import { useAuth } from "react-oidc-context";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
@@ -51,8 +58,8 @@ import { Textarea } from "../../components/ui/textarea";
|
|||||||
import {
|
import {
|
||||||
type ClientSummary,
|
type ClientSummary,
|
||||||
fetchClients,
|
fetchClients,
|
||||||
fetchDevStats,
|
|
||||||
fetchDeveloperRequestStatus,
|
fetchDeveloperRequestStatus,
|
||||||
|
fetchDevStats,
|
||||||
fetchMyTenants,
|
fetchMyTenants,
|
||||||
requestDeveloperAccess,
|
requestDeveloperAccess,
|
||||||
} from "../../lib/devApi";
|
} from "../../lib/devApi";
|
||||||
@@ -97,8 +104,7 @@ function ClientsPage() {
|
|||||||
} = useQuery({
|
} = useQuery({
|
||||||
queryKey: ["developer-request", tenantId],
|
queryKey: ["developer-request", tenantId],
|
||||||
queryFn: () => fetchDeveloperRequestStatus(tenantId),
|
queryFn: () => fetchDeveloperRequestStatus(tenantId),
|
||||||
enabled:
|
enabled: hasAccessToken && (role === "user" || role === "tenant_member"),
|
||||||
hasAccessToken && (role === "user" || role === "tenant_member"),
|
|
||||||
});
|
});
|
||||||
const { data: tenants } = useQuery({
|
const { data: tenants } = useQuery({
|
||||||
queryKey: ["myTenants"],
|
queryKey: ["myTenants"],
|
||||||
|
|||||||
@@ -20,11 +20,11 @@ import {
|
|||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "../../../components/ui/table";
|
} from "../../../components/ui/table";
|
||||||
|
import type { IdpConfig, IdpConfigCreateRequest } from "../../../lib/devApi";
|
||||||
import {
|
import {
|
||||||
createIdpConfigForClient,
|
createIdpConfigForClient,
|
||||||
listIdpConfigsForClient,
|
listIdpConfigsForClient,
|
||||||
} from "../../../lib/devApi";
|
} from "../../../lib/devApi";
|
||||||
import type { IdpConfig, IdpConfigCreateRequest } from "../../../lib/devApi";
|
|
||||||
import { t } from "../../../lib/i18n";
|
import { t } from "../../../lib/i18n";
|
||||||
|
|
||||||
// Proper Modal Component with Form
|
// Proper Modal Component with Form
|
||||||
@@ -178,9 +178,16 @@ const CreateIdpModal = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function ClientFederationPage() {
|
export function ClientFederationPage() {
|
||||||
const { id: clientId } = useParams<{ id: string }>();
|
const { id: clientIdParam } = useParams<{ id: string }>();
|
||||||
|
const clientId = clientIdParam ?? "";
|
||||||
const [isCreateModalOpen, setCreateModalOpen] = useState(false);
|
const [isCreateModalOpen, setCreateModalOpen] = useState(false);
|
||||||
|
|
||||||
|
const { data, isLoading, error } = useQuery({
|
||||||
|
queryKey: ["idpConfigs", clientId],
|
||||||
|
queryFn: () => listIdpConfigsForClient(clientId),
|
||||||
|
enabled: clientId.length > 0,
|
||||||
|
});
|
||||||
|
|
||||||
if (!clientId) {
|
if (!clientId) {
|
||||||
return (
|
return (
|
||||||
<div className="p-8 text-center text-destructive">
|
<div className="p-8 text-center text-destructive">
|
||||||
@@ -189,11 +196,6 @@ export function ClientFederationPage() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data, isLoading, error } = useQuery({
|
|
||||||
queryKey: ["idpConfigs", clientId],
|
|
||||||
queryFn: () => listIdpConfigsForClient(clientId),
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6 p-1">
|
<div className="space-y-6 p-1">
|
||||||
<PageHeader
|
<PageHeader
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import {
|
import {
|
||||||
ClipboardCheck,
|
|
||||||
CheckCircle2,
|
CheckCircle2,
|
||||||
|
ClipboardCheck,
|
||||||
Clock,
|
Clock,
|
||||||
Plus,
|
Plus,
|
||||||
ShieldAlert,
|
ShieldAlert,
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import {
|
|||||||
Activity,
|
Activity,
|
||||||
AlertTriangle,
|
AlertTriangle,
|
||||||
CheckCircle2,
|
CheckCircle2,
|
||||||
LayoutDashboard,
|
|
||||||
Layers3,
|
Layers3,
|
||||||
|
LayoutDashboard,
|
||||||
ShieldCheck,
|
ShieldCheck,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
@@ -18,12 +18,12 @@ import {
|
|||||||
} from "../../../../common/core/components/overview";
|
} from "../../../../common/core/components/overview";
|
||||||
import {
|
import {
|
||||||
type ClientSummary,
|
type ClientSummary,
|
||||||
type RPUsageDailyMetric,
|
|
||||||
type RPUsagePeriod,
|
|
||||||
fetchClients,
|
fetchClients,
|
||||||
|
fetchDeveloperRequestStatus,
|
||||||
fetchDevRPUsageDaily,
|
fetchDevRPUsageDaily,
|
||||||
fetchDevStats,
|
fetchDevStats,
|
||||||
fetchDeveloperRequestStatus,
|
type RPUsageDailyMetric,
|
||||||
|
type RPUsagePeriod,
|
||||||
} from "../../lib/devApi";
|
} from "../../lib/devApi";
|
||||||
import { t } from "../../lib/i18n";
|
import { t } from "../../lib/i18n";
|
||||||
import { resolveProfileRole } from "../../lib/role";
|
import { resolveProfileRole } from "../../lib/role";
|
||||||
@@ -723,7 +723,10 @@ function GlobalOverviewPage() {
|
|||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex h-8 items-center gap-1" aria-label="집계 단위">
|
<fieldset
|
||||||
|
className="flex h-8 items-center gap-1"
|
||||||
|
aria-label="집계 단위"
|
||||||
|
>
|
||||||
{[
|
{[
|
||||||
["day", t("ui.common.chart.period.day", "일")],
|
["day", t("ui.common.chart.period.day", "일")],
|
||||||
["week", t("ui.common.chart.period.week", "주")],
|
["week", t("ui.common.chart.period.week", "주")],
|
||||||
@@ -743,7 +746,7 @@ function GlobalOverviewPage() {
|
|||||||
{label}
|
{label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<OverviewSelectionChips
|
<OverviewSelectionChips
|
||||||
|
|||||||
@@ -26,7 +26,8 @@
|
|||||||
--input: 215 25% 24%;
|
--input: 215 25% 24%;
|
||||||
--ring: 209 79% 52%;
|
--ring: 209 79% 52%;
|
||||||
--radius: 0.75rem;
|
--radius: 0.75rem;
|
||||||
--app-background-image: radial-gradient(
|
--app-background-image:
|
||||||
|
radial-gradient(
|
||||||
circle at 10% 18%,
|
circle at 10% 18%,
|
||||||
rgba(54, 211, 153, 0.16),
|
rgba(54, 211, 153, 0.16),
|
||||||
transparent 28%
|
transparent 28%
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user