From 003dc60111a93c852217aef2c03a521d9c2a210f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B9=9B=E5=85=AE?= Date: Tue, 2 Jun 2026 14:46:39 +0800 Subject: [PATCH] Initial role user app --- .env.example | 2 + .gitignore | 10 + README.md | 100 + RTK.md | 14 + docs/C_EMPLOYEE_APP_REQUIREMENTS.md | 134 + docs/FULLSTACK_BACKEND_GAP_ANALYSIS.md | 209 + eslint.config.mjs | 11 + next-env.d.ts | 6 + next.config.ts | 32 + package.json | 27 + pnpm-lock.yaml | 3746 +++++++++++++++++ public/icon.svg | 13 + public/sw.js | 7 + src/app/(app)/announcements/[id]/page.tsx | 68 + src/app/(app)/announcements/page.tsx | 92 + src/app/(app)/dashboard/page.tsx | 168 + src/app/(app)/layout.tsx | 10 + src/app/(app)/loading.tsx | 10 + src/app/(app)/me/page.tsx | 52 + src/app/(app)/schedule/page.tsx | 87 + src/app/(app)/store/page.tsx | 71 + src/app/(app)/tasks/[id]/page.tsx | 114 + src/app/(app)/tasks/page.tsx | 88 + src/app/(auth)/login/page.tsx | 39 + src/app/api/auth/login/route.ts | 33 + src/app/api/auth/logout/route.ts | 15 + src/app/api/auth/me/password/route.ts | 11 + src/app/api/auth/me/route.ts | 6 + .../mobile/announcements/[id]/read/route.ts | 13 + .../api/mobile/announcements/[id]/route.ts | 11 + src/app/api/mobile/announcements/route.ts | 6 + src/app/api/mobile/shifts/route.ts | 7 + src/app/api/mobile/shifts/today/route.ts | 6 + src/app/api/mobile/store/employees/route.ts | 31 + src/app/api/mobile/store/route.ts | 38 + .../api/mobile/tasks/[id]/comment/route.ts | 16 + .../api/mobile/tasks/[id]/complete/route.ts | 13 + src/app/api/mobile/tasks/[id]/route.ts | 11 + src/app/api/mobile/tasks/[id]/start/route.ts | 13 + src/app/api/mobile/tasks/route.ts | 6 + src/app/api/permissions/me/route.ts | 6 + src/app/globals.css | 1109 +++++ src/app/layout.tsx | 33 + src/app/manifest.ts | 21 + src/app/page.tsx | 5 + src/components/announcement-read-marker.tsx | 27 + src/components/bottom-nav.tsx | 33 + src/components/empty-state.tsx | 16 + src/components/filter-nav.tsx | 35 + src/components/login-form.tsx | 83 + src/components/logout-button.tsx | 24 + src/components/page-header.tsx | 15 + src/components/password-change-form.tsx | 84 + src/components/status-pill.tsx | 8 + src/components/task-action-panel.tsx | 145 + src/lib/backend.ts | 94 + src/lib/format.ts | 14 + src/lib/mobile-data.ts | 512 +++ src/lib/session.ts | 29 + src/lib/types.ts | 136 + src/proxy.ts | 26 + tsconfig.json | 34 + 62 files changed, 7835 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 README.md create mode 100644 RTK.md create mode 100644 docs/C_EMPLOYEE_APP_REQUIREMENTS.md create mode 100644 docs/FULLSTACK_BACKEND_GAP_ANALYSIS.md create mode 100644 eslint.config.mjs create mode 100644 next-env.d.ts create mode 100644 next.config.ts create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 public/icon.svg create mode 100644 public/sw.js create mode 100644 src/app/(app)/announcements/[id]/page.tsx create mode 100644 src/app/(app)/announcements/page.tsx create mode 100644 src/app/(app)/dashboard/page.tsx create mode 100644 src/app/(app)/layout.tsx create mode 100644 src/app/(app)/loading.tsx create mode 100644 src/app/(app)/me/page.tsx create mode 100644 src/app/(app)/schedule/page.tsx create mode 100644 src/app/(app)/store/page.tsx create mode 100644 src/app/(app)/tasks/[id]/page.tsx create mode 100644 src/app/(app)/tasks/page.tsx create mode 100644 src/app/(auth)/login/page.tsx create mode 100644 src/app/api/auth/login/route.ts create mode 100644 src/app/api/auth/logout/route.ts create mode 100644 src/app/api/auth/me/password/route.ts create mode 100644 src/app/api/auth/me/route.ts create mode 100644 src/app/api/mobile/announcements/[id]/read/route.ts create mode 100644 src/app/api/mobile/announcements/[id]/route.ts create mode 100644 src/app/api/mobile/announcements/route.ts create mode 100644 src/app/api/mobile/shifts/route.ts create mode 100644 src/app/api/mobile/shifts/today/route.ts create mode 100644 src/app/api/mobile/store/employees/route.ts create mode 100644 src/app/api/mobile/store/route.ts create mode 100644 src/app/api/mobile/tasks/[id]/comment/route.ts create mode 100644 src/app/api/mobile/tasks/[id]/complete/route.ts create mode 100644 src/app/api/mobile/tasks/[id]/route.ts create mode 100644 src/app/api/mobile/tasks/[id]/start/route.ts create mode 100644 src/app/api/mobile/tasks/route.ts create mode 100644 src/app/api/permissions/me/route.ts create mode 100644 src/app/globals.css create mode 100644 src/app/layout.tsx create mode 100644 src/app/manifest.ts create mode 100644 src/app/page.tsx create mode 100644 src/components/announcement-read-marker.tsx create mode 100644 src/components/bottom-nav.tsx create mode 100644 src/components/empty-state.tsx create mode 100644 src/components/filter-nav.tsx create mode 100644 src/components/login-form.tsx create mode 100644 src/components/logout-button.tsx create mode 100644 src/components/page-header.tsx create mode 100644 src/components/password-change-form.tsx create mode 100644 src/components/status-pill.tsx create mode 100644 src/components/task-action-panel.tsx create mode 100644 src/lib/backend.ts create mode 100644 src/lib/format.ts create mode 100644 src/lib/mobile-data.ts create mode 100644 src/lib/session.ts create mode 100644 src/lib/types.ts create mode 100644 src/proxy.ts create mode 100644 tsconfig.json diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..6d30546 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +ACCESS_MANAGE_API_BASE_URL=http://localhost:3500/api +ROLE_USER_SESSION_COOKIE=role_user_session diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6069efd --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +.next +node_modules +out +dist +.env +.env*.local +*.log +tsconfig.tsbuildinfo +.playwright-mcp +role-user-*.png diff --git a/README.md b/README.md new file mode 100644 index 0000000..f34f650 --- /dev/null +++ b/README.md @@ -0,0 +1,100 @@ +# role-user + +员工端 C 端工作台,基于 `access-manage` 后端和 `role-admin` 管理后台建设。 + +当前已搭建 React + Next.js App Router + TypeScript 前端骨架,采用移动优先、PWA 友好的工作台形态。 + +## 启动 + +```bash +pnpm install +cp .env.example .env.local +pnpm dev +``` + +默认访问: + +- 登录页:http://localhost:3210/login +- 工作台:http://localhost:3210/dashboard +- 任务:http://localhost:3210/tasks +- 排班:http://localhost:3210/schedule +- 公告:http://localhost:3210/announcements +- 门店:http://localhost:3210/store +- 我的:http://localhost:3210/me + +## 环境变量 + +```bash +ACCESS_MANAGE_API_BASE_URL=http://localhost:3500/api +ROLE_USER_SESSION_COOKIE=role_user_session +``` + +`ACCESS_MANAGE_API_BASE_URL` 指向 `access-manage` 服务端 API 根路径。员工登录会调用后端 `POST /api/auth/employee/login`,登录成功后只把 JWT 写入 Next.js 服务端 HttpOnly Cookie,不写入 `localStorage`。 + +## 可用命令 + +```bash +pnpm dev +pnpm typecheck +pnpm lint +pnpm build +``` + +## 当前实现范围 + +- Next.js App Router 项目结构、TypeScript、ESLint、`@/*` 路径别名和基础样式。 +- BFF Route Handlers: + - `POST /api/auth/login` + - `POST|GET /api/auth/logout` + - `GET /api/auth/me` + - `PATCH /api/auth/me/password` + - `GET /api/permissions/me` + - `GET /api/mobile/tasks` + - `GET /api/mobile/tasks/:id` + - `POST /api/mobile/tasks/:id/start` + - `POST /api/mobile/tasks/:id/complete` + - `POST /api/mobile/tasks/:id/comment` + - `GET /api/mobile/announcements` + - `GET /api/mobile/announcements/:id` + - `POST /api/mobile/announcements/:id/read` + - `GET /api/mobile/shifts` + - `GET /api/mobile/shifts/today` + - `GET /api/mobile/store` + - `GET /api/mobile/store/employees` +- 页面: + - `/login` + - `/dashboard` + - `/tasks` + - `/tasks/:id` + - `/schedule` + - `/announcements` + - `/announcements/:id` + - `/store` + - `/me` +- 首页已消费正式 `mobile/bootstrap` 的 `latestAnnouncements`、`tasks`、`overdueTaskCount`、`todayShift/todayShifts`。 +- `/announcements` 支持全部、未读、已读筛选;公告详情进入后通过 BFF 标记已读。 +- `/tasks` 支持全部、待处理、处理中、已完成筛选;任务详情支持开始、完成和追加备注。 +- `/schedule` 支持今日、未来、全部排班筛选。 +- `/me` 已接入本人修改密码表单,前端不持久化密码或 token。 +- `/dashboard`、`/tasks`、`/announcements`、`/schedule` 会在后端临时不可用或 5xx 时保持基础资料和业务空态。 +- `/store` 会尝试读取当前门店详情和本店员工列表;后端权限或接口不可用时保持门店基础信息和空态。 +- 移动端底部导航、加载态、空态、会话缺失跳登录、401 清理 Cookie 的服务端路径。 +- PWA manifest、应用图标、基础 service worker、安全响应头和参照 SeaCloud 的移动端工作台视觉系统。 + +## 文档 + +- `docs/C_EMPLOYEE_APP_REQUIREMENTS.md`: C 端员工工作台需求文档。 +- `docs/FULLSTACK_BACKEND_GAP_ANALYSIS.md`: C 端员工工作台三端缺口与改动范围分析。 +- `RTK.md`: 本项目 Codex/Agent 协作规则。 + +## 关联项目 + +- `/Users/mac033/Desktop/my-project/access-manage`: 服务端 API。 +- `/Users/mac033/Desktop/my-project/role-admin`: 管理后台。 + +## 安全边界 + +- 不展示任何明文密码。 +- 不把密码、JWT 或权限 token 持久化到前端存储。 +- 员工端修改密码已通过 BFF 接入后端 `PATCH /api/auth/me/password`。 +- 公告、任务、排班已按正式后端接口接入列表、筛选、详情和员工端操作;后端临时不可用时页面保留业务空态。 diff --git a/RTK.md b/RTK.md new file mode 100644 index 0000000..8bf4e04 --- /dev/null +++ b/RTK.md @@ -0,0 +1,14 @@ +# role-user Project Notes + +This project is the employee-facing C-side app for the `access-manage` backend and the `role-admin` management console. + +When working in this repository: + +- Prefer Chinese for project notes, PRDs, comments, and delivery summaries. +- Use React + Next.js App Router + TypeScript as the default frontend stack. +- Keep the app mobile-first and PWA-friendly; desktop should work, but the primary user is a store employee using a phone. +- Treat `access-manage/docs/ROLE_USER_BACKEND_REQUIREMENTS.md` as the backend contract source and `docs/C_EMPLOYEE_APP_REQUIREMENTS.md` as the frontend product source. +- Do not store or expose plaintext passwords in the frontend. Password operations must use reset/change flows, temporary one-time passwords, and audit logs. +- Use a Backend-for-Frontend layer in Next.js Route Handlers when it improves session safety. Prefer HttpOnly cookies over localStorage for tokens. +- Keep business API types and request helpers centralized under `src/lib/` once the app is scaffolded. +- When files or directories are added, removed, renamed, or reorganized, update `README.md` in the same change once a README exists. diff --git a/docs/C_EMPLOYEE_APP_REQUIREMENTS.md b/docs/C_EMPLOYEE_APP_REQUIREMENTS.md new file mode 100644 index 0000000..83e99a1 --- /dev/null +++ b/docs/C_EMPLOYEE_APP_REQUIREMENTS.md @@ -0,0 +1,134 @@ +# role-user C 端员工工作台需求文档 + +## 1. 项目定位 + +`role-user` 是门店员工使用的 C 端工作台,和现有两个项目形成三端分工: + +| 项目 | 定位 | 主要用户 | +| --- | --- | --- | +| `access-manage` | 服务端 API、鉴权、权限、门店数据和业务数据 | 所有前端 | +| `role-admin` | 管理后台,维护门店、员工、角色、权限和 C 端运营内容 | 超级管理员、管理员、店长 | +| `role-user` | 员工端 C 端应用,承载日常工作、通知、任务、排班和个人中心 | 店长、收银员、后厨、兼职 | + +本项目不是另一个管理后台,也不是顾客下单端。第一版正式产品应聚焦“员工每天打开后能完成门店日常工作”。 + +## 2. 用户角色 + +| 角色 | 典型职责 | C 端能力 | +| --- | --- | --- | +| 店长 `store_manager` | 管理本店员工、查看门店任务和公告 | 查看本店员工、处理门店任务、查看排班和公告 | +| 收银员 `cashier` | 收银、核对订单、基础会员操作 | 查看个人任务、公告、排班和门店信息 | +| 后厨 `kitchen` | 出品、备货、库存协作 | 查看后厨任务、公告、排班 | +| 兼职 `part_time` | 临时排班和基础任务 | 查看个人任务、排班、公告 | +| 管理员 `admin` | 管理多门店业务 | 默认走 `role-admin`,如进入 C 端则展示员工视角 | + +## 3. 功能范围 + +### 3.1 登录与会话 + +- 员工使用手机号和密码登录。 +- 登录接口走 `POST /api/auth/employee/login`。 +- 登录成功后读取 `GET /api/auth/me` 和 `GET /api/permissions/me`。 +- Next.js BFF 负责保存服务端会话,前端不直接把 JWT 放进 `localStorage`。 +- 后端没有 refresh token 时,401 直接清理会话并跳回登录页。 + +### 3.2 工作台首页 + +首页用于高频扫描,不做营销页: + +- 今日身份卡:员工姓名、所属门店、角色、账号状态。 +- 今日排班:当前班次、上下班时间、岗位。 +- 待办任务:未完成、即将超时、已逾期数量和列表入口。 +- 最新公告:未读公告摘要。 +- 快捷入口:任务、排班、门店、我的。 + +### 3.3 公告中心 + +- 展示管理员或店长发布给当前员工、角色或门店的公告。 +- 支持未读、已读筛选。 +- 进入公告详情后标记已读。 +- 重要公告需要在首页突出显示。 + +### 3.4 任务中心 + +- 展示分配给当前员工或当前门店的任务。 +- 状态:待处理、处理中、已完成、已取消。 +- 支持按状态、截止时间筛选。 +- 员工可以更新自己的任务状态并填写处理备注。 +- 店长可以查看本店任务汇总;跨店管理仍放在后台。 + +### 3.5 排班 + +- 展示个人本周/本月排班。 +- 支持今日班次和未来班次。 +- 第一版只做查看,不做员工端自行换班。 + +### 3.6 门店信息 + +- 展示当前门店名称、地址、电话、状态。 +- 店长或有 `employee:view:store` 权限的员工可查看本店员工列表。 +- 不在 C 端提供门店新增、删除、角色分配等后台能力。 + +### 3.7 我的 + +- 展示个人资料、手机号、门店、角色、最近登录时间。 +- 支持修改自己的登录密码:旧密码 + 新密码。 +- 支持退出登录。 +- 不支持查看任何人的明文密码。 + +## 4. 密码与凭据安全要求 + +用户提出“超级管理员与管理员需要支持查看自身和下级用户密码”。正式实现不做明文密码展示,原因: + +- 当前后端只保存 `password_hash`,技术上无法反查原密码。 +- 如果为了展示密码而保存明文或可逆密文,会扩大泄露面,不适合作为正式产品默认能力。 + +正式替代方案: + +- 后台提供“重置下级用户密码”。 +- 重置后生成一次性临时密码,只在本次响应和后台弹窗中显示一次。 +- 员工下次登录后应被要求修改密码。 +- 所有密码重置行为写入审计日志:操作者、目标用户、时间、IP、User-Agent、原因。 +- 本人密码只支持“修改密码”,不支持“查看当前密码”。 + +## 5. 信息架构 + +底部导航: + +| 路由 | 页面 | 说明 | +| --- | --- | --- | +| `/dashboard` | 工作台 | 默认首页 | +| `/tasks` | 任务 | 个人和本店任务 | +| `/schedule` | 排班 | 个人排班 | +| `/announcements` | 公告 | 公告列表和详情 | +| `/me` | 我的 | 个人资料、改密、退出 | + +辅助路由: + +| 路由 | 页面 | +| --- | --- | +| `/login` | 登录 | +| `/store` | 当前门店 | +| `/tasks/:id` | 任务详情 | +| `/announcements/:id` | 公告详情 | + +## 6. 技术要求 + +- 使用 Next.js App Router、React、TypeScript。 +- 移动优先,支持 PWA 安装。 +- 使用 Server Components 承载只读数据首屏,交互表单使用 Client Components。 +- 使用 Route Handlers 做 BFF:`role-user` 调自己的 `/api/*`,BFF 再访问 `access-manage`。 +- 会话 token 通过 HttpOnly Cookie 保存。 +- 服务端请求用户态接口时禁用共享缓存。 +- 首页独立数据并行请求,避免瀑布。 +- API 类型集中维护,不在组件里散落接口路径。 + +## 7. 验收标准 + +- 员工可以使用手机号和密码登录。 +- 登录后能看到自己的门店、角色、权限和基础信息。 +- 底部导航在移动端可用,桌面端布局不破。 +- 401 后清理会话并回到登录页。 +- 密码不会出现在前端持久化存储中。 +- 没有任何明文密码查询或展示接口。 +- 管理后台重置出的临时密码仅显示一次,并有审计记录。 diff --git a/docs/FULLSTACK_BACKEND_GAP_ANALYSIS.md b/docs/FULLSTACK_BACKEND_GAP_ANALYSIS.md new file mode 100644 index 0000000..c9aba5f --- /dev/null +++ b/docs/FULLSTACK_BACKEND_GAP_ANALYSIS.md @@ -0,0 +1,209 @@ +# C 端员工工作台三端缺口分析 + +生成时间:2026-06-01 + +## 1. 结论 + +当前三端已经开始围绕 C 端员工工作台建设,但还没有完全闭环。 + +| 端 | 项目 | 当前状态 | 是否需要改 | +| --- | --- | --- | --- | +| 后端 | `access-manage` | 已新增公告、任务、排班、移动端、凭据审计模块,但首屏聚合、审计筛选、部分数据字段和业务校验还缺 | 是 | +| 后台 | `role-admin` | 已新增公告、任务、排班、凭据审计页面,但 API 类型仍按早期命名设计,和后端正式字段不一致 | 是 | +| 前台 | `role-user` | 已有移动端页面、BFF 和空态,但缺任务/公告详情、任务操作、改密表单、门店详情和首屏聚合字段消费完善 | 是 | + +优先级建议: + +1. 先补齐 `access-manage` 合同,保证 API 返回稳定。 +2. 再修正 `role-admin` API 适配,保证运营内容能创建出来。 +3. 最后完善 `role-user` 的员工端交互,消费这些正式接口。 + +## 2. 后端缺口 + +### 2.1 首屏聚合 `/api/mobile/bootstrap` 数据不足 + +现状: + +- 已返回 `user`、`store`、`permissions`、`counters.unreadAnnouncementCount`、`counters.pendingTaskCount`、`todayShifts`。 +- 没有返回首页需要直接展示的 `latestAnnouncements`、`tasks`。 +- 没有返回 `overdueTaskCount`。 +- `todayShifts` 是数组,员工端当前兼容数组,但接口语义应明确。 + +建议: + +- 增加最近公告列表,最多 3 条。 +- 增加待办任务列表,最多 5 条。 +- 增加逾期任务数量。 +- 明确返回 `todayShift` 或 `todayShifts`,前端统一消费。 + +### 2.2 凭据审计接口筛选和字段不完整 + +现状: + +- `GET /api/admin/credential-audits` schema 只接收 `targetEmployeeId`、`page`、`pageSize`。 +- `role-admin` 页面已经提交 `operatorId`、`storeId`、`startDate`、`endDate`,但后端没有完整接收。 +- 返回值缺 `targetEmployeePhone`、`storeName`,后台页面正在展示这些字段。 + +建议: + +- 后端 query schema 增加 `operatorId`、`storeId`、`startDate`、`endDate`。 +- repository join `stores`,返回目标员工手机号和门店名。 +- 明确操作者筛选对超级管理员和员工操作者的匹配规则。 + +### 2.3 后台运营字段合同与 `role-admin` 不一致 + +后端当前正式字段: + +- 公告:`level`、`targetType`、`targets`。 +- 任务:`dueAt`、`assignees`、`CANCELLED`。 +- 排班:`roleName`、`CANCELLED`。 +- 凭据审计:`actorName`、`createdAt`。 + +`role-admin` 当前字段: + +- 公告:`importance`、`targetScope`、`targetStoreIds`、`targetRoleIds`、`targetEmployeeIds`。 +- 任务:`deadlineAt`、`assigneeIds`、`assigneeNames`、`CANCELED`。 +- 排班:`position`、`CANCELED`、`COMPLETED`。 +- 凭据审计:`operatorName`、`operatedAt`、`targetEmployeePhone`、`storeName`。 + +建议: + +- 优先在 `role-admin/src/api/access.ts` 做请求/响应适配,避免改动多个页面。 +- 后端只补真正缺失的字段和筛选,不为了旧前端命名反向污染 API。 + +### 2.4 任务业务闭环还需加强 + +现状: + +- 后台任务创建要求 `assigneeIds` 至少 1 个。 +- C 端只能查到分配给自己的任务。 +- 需求文档提到“个人和门店任务”,但后端还没有店铺级任务被所有本店员工可见的明确规则。 + +建议: + +- 第一版若只做员工分配任务,应更新文档说明。 +- 若要支持门店任务,需要允许 `assigneeIds` 为空,并把 `store_id` 命中的本店任务纳入 `/api/mobile/tasks`。 + +### 2.5 排班冲突校验缺失 + +现状: + +- 已校验员工属于门店、结束时间晚于开始时间。 +- 需求文档要求“同一员工同一时间段不能重复排班”,后端还没有冲突检测。 + +建议: + +- `create` 和 `update` 时检测同一员工未取消班次时间段重叠。 +- 更新时排除当前排班 ID。 + +### 2.6 文档与实现需要同步 + +现状: + +- `access-manage/docs/API.md` 已写入新增接口,但部分字段仍偏概要。 +- `docs/ROLE_USER_BACKEND_REQUIREMENTS.md` 是目标合同,未标注当前缺口状态。 + +建议: + +- 后端实现完成后补充 `bootstrap` 响应示例。 +- 补充凭据审计筛选参数和返回字段。 + +## 3. 后台改动范围 + +项目:`/Users/mac033/Desktop/my-project/role-admin` + +需要改: + +- `src/api/access.ts` + - 把公告表单字段转换为后端 `level`、`targetType`、`targets`。 + - 把后端公告返回转换为页面使用的 `importance`、`targetScope`、目标 ID 数组。 + - 把任务 `deadlineAt` 转为 `dueAt`,`assignees` 转为 `assigneeIds` 和 `assigneeNames`。 + - 统一 `CANCELLED` 与页面当前 `CANCELED` 的枚举。 + - 把排班 `position` 转为 `roleName`。 + - 把凭据审计 `actorName/createdAt` 转为 `operatorName/operatedAt`,并接收新增字段。 +- 视图页面只在必要时微调,优先不大面积重写。 +- `README.md` 同步新增模块和接口适配说明。 + +## 4. 前台改动范围 + +项目:`/Users/mac033/Desktop/my-project/role-user` + +需要改: + +- `src/lib/mobile-data.ts` + - 适配后端正式 `bootstrap` 的最新公告、任务、逾期数。 + - 明确 `todayShift/todayShifts` 消费策略。 +- BFF Route Handlers + - 增加公告详情、标记已读。 + - 增加任务详情、开始、完成、备注。 + - 增加本人修改密码。 + - 可选:当前门店详情和本店员工列表代理。 +- 页面 + - `/announcements/:id` 公告详情并标记已读。 + - `/tasks/:id` 任务详情、开始、完成、备注。 + - `/me` 改密表单。 + - `/store` 接入门店地址、电话和本店员工列表。 + +## 5. 拆分给子进程的任务 + +### 子进程 A:后端 `access-manage` + +目标: + +- 补齐 `mobile/bootstrap` 的首页数据。 +- 补齐凭据审计筛选和返回字段。 +- 增加排班冲突校验。 +- 根据选择决定是否支持门店级任务。 +- 更新 `docs/API.md`。 + +写入范围: + +- `src/modules/mobile/*` +- `src/modules/tasks/*` +- `src/modules/shifts/*` +- `src/modules/credentials/*` +- `docs/API.md` +- 必要时新增迁移,但优先复用现有表结构。 + +### 子进程 B:后台 `role-admin` + +目标: + +- 修正 `src/api/access.ts` 与后端正式合同的字段适配。 +- 保持页面现有交互不大改。 +- 修正枚举值和凭据审计展示。 +- 更新 `README.md`。 + +写入范围: + +- `src/api/access.ts` +- 必要时 `src/views/announcements/index.vue` +- 必要时 `src/views/tasks/index.vue` +- 必要时 `src/views/shifts/index.vue` +- 必要时 `src/views/credential-audits/index.vue` +- `README.md` + +### 子进程 C:前台 `role-user` + +目标: + +- 完成员工端详情和操作闭环。 +- 接入本人修改密码。 +- 接入门店详情和本店员工列表。 +- 更新 `README.md`。 + +写入范围: + +- `src/lib/*` +- `src/app/api/*` +- `src/app/(app)/*` +- `src/components/*` +- `README.md` + +## 6. 验收顺序 + +1. 后端 `pnpm typecheck` 或现有检查命令通过。 +2. 后台能用超级管理员创建公告、任务、排班,重置密码并看到审计。 +3. 前台员工登录后能看到首页列表数据,进入详情并完成任务/公告操作。 +4. 401 会清理员工端会话,不在浏览器存储 JWT 或密码。 +5. 不存在明文密码查看接口。 diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..09f28dc --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,11 @@ +import { defineConfig, globalIgnores } from "eslint/config"; +import nextVitals from "eslint-config-next/core-web-vitals"; +import nextTs from "eslint-config-next/typescript"; + +const eslintConfig = defineConfig([ + ...nextVitals, + ...nextTs, + globalIgnores([".next/**", "out/**", "build/**", "node_modules/**", "next-env.d.ts"]) +]); + +export default eslintConfig; diff --git a/next-env.d.ts b/next-env.d.ts new file mode 100644 index 0000000..9edff1c --- /dev/null +++ b/next-env.d.ts @@ -0,0 +1,6 @@ +/// +/// +import "./.next/types/routes.d.ts"; + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/next.config.ts b/next.config.ts new file mode 100644 index 0000000..bbb0989 --- /dev/null +++ b/next.config.ts @@ -0,0 +1,32 @@ +import path from "node:path"; +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + poweredByHeader: false, + typedRoutes: true, + turbopack: { + root: path.resolve(__dirname) + }, + async headers() { + return [ + { + source: "/(.*)", + headers: [ + { key: "X-Content-Type-Options", value: "nosniff" }, + { key: "X-Frame-Options", value: "DENY" }, + { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" } + ] + }, + { + source: "/sw.js", + headers: [ + { key: "Content-Type", value: "application/javascript; charset=utf-8" }, + { key: "Cache-Control", value: "no-cache, no-store, must-revalidate" }, + { key: "Content-Security-Policy", value: "default-src 'self'; script-src 'self'" } + ] + } + ]; + } +}; + +export default nextConfig; diff --git a/package.json b/package.json new file mode 100644 index 0000000..f5e6e53 --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "role-user", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev -p 3210", + "build": "next build", + "start": "next start", + "lint": "eslint", + "typecheck": "tsc --noEmit --incremental false" + }, + "dependencies": { + "lucide-react": "^0.511.0", + "next": "latest", + "react": "latest", + "react-dom": "latest" + }, + "devDependencies": { + "@eslint/eslintrc": "^3.3.1", + "@types/node": "^22.15.24", + "@types/react": "^19.1.6", + "@types/react-dom": "^19.1.5", + "eslint": "^9.28.0", + "eslint-config-next": "latest", + "typescript": "^5.8.3" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..b101c42 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,3746 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + lucide-react: + specifier: ^0.511.0 + version: 0.511.0(react@19.2.6) + next: + specifier: latest + version: 16.2.6(@babel/core@7.29.7)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: + specifier: latest + version: 19.2.6 + react-dom: + specifier: latest + version: 19.2.6(react@19.2.6) + devDependencies: + '@eslint/eslintrc': + specifier: ^3.3.1 + version: 3.3.5 + '@types/node': + specifier: ^22.15.24 + version: 22.19.19 + '@types/react': + specifier: ^19.1.6 + version: 19.2.15 + '@types/react-dom': + specifier: ^19.1.5 + version: 19.2.3(@types/react@19.2.15) + eslint: + specifier: ^9.28.0 + version: 9.39.4 + eslint-config-next: + specifier: latest + version: 16.2.6(@typescript-eslint/parser@8.60.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3) + typescript: + specifier: ^5.8.3 + version: 5.9.3 + +packages: + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.7': + resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@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/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + 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/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.7': + resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.2': + resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.5': + resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.4': + resolution: {integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@napi-rs/wasm-runtime@1.1.4': + resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + + '@next/env@16.2.6': + resolution: {integrity: sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw==} + + '@next/eslint-plugin-next@16.2.6': + resolution: {integrity: sha512-Z8l6o4JWKUl755x4R+wogD86KPeU+Ckw4K+SYG4kHeOJtRenDeK+OSbGcqZpDtbwn9DsJVdir2UxmwXuinUbUw==} + + '@next/swc-darwin-arm64@16.2.6': + resolution: {integrity: sha512-ZJGkkcNfYgrrMkqOdZ7zoLa1TOy0qpcMfk/z4Mh/FKUz40gVO+HNQWqmLxf67Z5WB64DRp0dhEbyHfel+6sJUg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@next/swc-darwin-x64@16.2.6': + resolution: {integrity: sha512-v/YLBHIY132Ced3puBJ7YJKw1lqsCrgcNo2aRJlCEyQrrCeRJlvGlnmxhPxNQI3KE3N1DN5r9TPNPvka3nq5RQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@next/swc-linux-arm64-gnu@16.2.6': + resolution: {integrity: sha512-RPOvqlYBbcQjkz9VQQDZ2T2bARIjXZV1KFlt+V2Mr6SW/e4I9fcKsaA0hdyf2FHoTlsV2xnBd5Y912rP/1Ce6w==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@next/swc-linux-arm64-musl@16.2.6': + resolution: {integrity: sha512-URUTu1+dMkxJsPFgm+OeEvq9wf5sujw0EvgYy80TDGHTSLTnIHeqb0Eu8A3sC95IRgjejQL+kC4mw+4yPxiAXA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@next/swc-linux-x64-gnu@16.2.6': + resolution: {integrity: sha512-DOj182mPV8G3UkrayLoREM5YEYI+Dk5wv7Ox9xl1fFibAELEsFD0lDPfHIeILlutMMfdyhlzYPELG3peuKaurw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@next/swc-linux-x64-musl@16.2.6': + resolution: {integrity: sha512-HKQ5SP/V/ub73UvF7n/zeJlxk2kLmtL7Wzrg4WfmkjmNos5onJ2tKu7yZOPdL18A6Svfn3max29ym+ry7NkK4g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@next/swc-win32-arm64-msvc@16.2.6': + resolution: {integrity: sha512-LZXpTlPyS5v7HhSmnvsLGP3iIYgYOBnc8r8ArlT55sGHV89bR2HlDdBjWQ+PY6SJMmk8TuVGFuxalnP3k/0Dwg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@next/swc-win32-x64-msvc@16.2.6': + resolution: {integrity: sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@nolyfill/is-core-module@1.0.39': + resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} + engines: {node: '>=12.4.0'} + + '@rtsao/scc@1.1.0': + resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} + + '@swc/helpers@0.5.15': + resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + + '@tybys/wasm-util@0.10.2': + resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/json5@0.0.29': + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + + '@types/node@22.19.19': + resolution: {integrity: sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.15': + resolution: {integrity: sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==} + + '@typescript-eslint/eslint-plugin@8.60.0': + resolution: {integrity: sha512-QYb/sa74/s7OKMbACMjrYnGspj9Hs5YI5aaffSL65UfeBUzVzBJfVo3oWSpbzPurvm7yaCCo2Lk7lVj610HqKw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.60.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/parser@8.60.0': + resolution: {integrity: sha512-fcqpj/MyK4sxDPcbe7STNPbpQL4RLZOPWuaTmwZYuc+hJKzRf58yRxfhqGpc6PIq9ZyfSBpfHgmUHmHs0KwHwg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/project-service@8.60.0': + resolution: {integrity: sha512-aZu74NNKJeUWqCjDddzdiKaS82dgYgV/vmf+Ui3ZdZejmgfXR/q+pRumgobnQ2cCJTgGTWp4ypiwsuofFubavg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/scope-manager@8.60.0': + resolution: {integrity: sha512-pFzqhllJMs+jghLQWzV00ds39xLzuyqPSev5pd8f4Ir0rtKR3ZLUB4/4dhjOFighWb9larvtfJvqL+4yKDI3Xw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.60.0': + resolution: {integrity: sha512-BZPR3RGYlAXnly6ymAxfkVn5rCbZzQNou0rxv3GfWZ8cTQp+hhVd73khbGLAd8k1TlAPLISH337M+tAgAnaJDQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/type-utils@8.60.0': + resolution: {integrity: sha512-SX46wEUtitCpq7AN38HkUU/+zvUpdKf7ephtWAFgckH8O7PQIyL5gvrhQgBLuEYgLfuKWOVvWVskMbuFHAz5xg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/types@8.60.0': + resolution: {integrity: sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.60.0': + resolution: {integrity: sha512-3AcZNBGMClm6CXDyo8kYvVGT/sx29sS0oBsIb9oZI2gunA4Vm2M3YHzRLPvsUBBsl+yB5FPtltq7gGH0iTlp9g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.60.0': + resolution: {integrity: sha512-HtXuPfrHTyBDkameWpl+vJb1Uevu2tznAyahM1Oc4AENidCLTPiZDWIo4GfcxNdC/RcfGcadzzkqbRG87dUrQA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/visitor-keys@8.60.0': + resolution: {integrity: sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@unrs/resolver-binding-android-arm-eabi@1.12.2': + resolution: {integrity: sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==} + cpu: [arm] + os: [android] + + '@unrs/resolver-binding-android-arm64@1.12.2': + resolution: {integrity: sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==} + cpu: [arm64] + os: [android] + + '@unrs/resolver-binding-darwin-arm64@1.12.2': + resolution: {integrity: sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==} + cpu: [arm64] + os: [darwin] + + '@unrs/resolver-binding-darwin-x64@1.12.2': + resolution: {integrity: sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==} + cpu: [x64] + os: [darwin] + + '@unrs/resolver-binding-freebsd-x64@1.12.2': + resolution: {integrity: sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==} + cpu: [x64] + os: [freebsd] + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.12.2': + resolution: {integrity: sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm-musleabihf@1.12.2': + resolution: {integrity: sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm64-gnu@1.12.2': + resolution: {integrity: sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==} + cpu: [arm64] + os: [linux] + + '@unrs/resolver-binding-linux-arm64-musl@1.12.2': + resolution: {integrity: sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==} + cpu: [arm64] + os: [linux] + + '@unrs/resolver-binding-linux-loong64-gnu@1.12.2': + resolution: {integrity: sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==} + cpu: [loong64] + os: [linux] + + '@unrs/resolver-binding-linux-loong64-musl@1.12.2': + resolution: {integrity: sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==} + cpu: [loong64] + os: [linux] + + '@unrs/resolver-binding-linux-ppc64-gnu@1.12.2': + resolution: {integrity: sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==} + cpu: [ppc64] + os: [linux] + + '@unrs/resolver-binding-linux-riscv64-gnu@1.12.2': + resolution: {integrity: sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==} + cpu: [riscv64] + os: [linux] + + '@unrs/resolver-binding-linux-riscv64-musl@1.12.2': + resolution: {integrity: sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==} + cpu: [riscv64] + os: [linux] + + '@unrs/resolver-binding-linux-s390x-gnu@1.12.2': + resolution: {integrity: sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==} + cpu: [s390x] + os: [linux] + + '@unrs/resolver-binding-linux-x64-gnu@1.12.2': + resolution: {integrity: sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==} + cpu: [x64] + os: [linux] + + '@unrs/resolver-binding-linux-x64-musl@1.12.2': + resolution: {integrity: sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==} + cpu: [x64] + os: [linux] + + '@unrs/resolver-binding-openharmony-arm64@1.12.2': + resolution: {integrity: sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==} + cpu: [arm64] + os: [openharmony] + + '@unrs/resolver-binding-wasm32-wasi@1.12.2': + resolution: {integrity: sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@unrs/resolver-binding-win32-arm64-msvc@1.12.2': + resolution: {integrity: sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==} + cpu: [arm64] + os: [win32] + + '@unrs/resolver-binding-win32-ia32-msvc@1.12.2': + resolution: {integrity: sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==} + cpu: [ia32] + os: [win32] + + '@unrs/resolver-binding-win32-x64-msvc@1.12.2': + resolution: {integrity: sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==} + cpu: [x64] + os: [win32] + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array-includes@3.1.9: + resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlast@1.2.5: + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlastindex@1.2.6: + resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} + + array.prototype.tosorted@1.1.4: + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + + ast-types-flow@0.0.8: + resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} + + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + axe-core@4.11.4: + resolution: {integrity: sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA==} + engines: {node: '>=4'} + + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + baseline-browser-mapping@2.10.33: + resolution: {integrity: sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw==} + engines: {node: '>=6.0.0'} + hasBin: true + + brace-expansion@1.1.15: + resolution: {integrity: sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==} + + brace-expansion@5.0.6: + resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} + engines: {node: 18 || 20 || >=22} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.9: + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001793: + resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + client-only@0.0.1: + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + damerau-levenshtein@1.0.8: + resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} + + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + electron-to-chromium@1.5.364: + resolution: {integrity: sha512-G/dYE3+AYhyHwzTwg8UbnXf7zqMERYh7l2jJ3QujhFsH8agSYwtnGAR2aZ7f0AakIKJXd5En/Hre4igIUrdlYw==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + es-abstract@1.24.2: + resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-iterator-helpers@1.3.2: + resolution: {integrity: sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-config-next@16.2.6: + resolution: {integrity: sha512-z2ELYSkyrrJ6cuunTU8vhsT/RpouPkjaSah06nVW6Rg2Hpg0Vs8s497/e5s8G8qtdp4ccsiovz5P1rv+5VSW2Q==} + peerDependencies: + eslint: '>=9.0.0' + typescript: '>=3.3.1' + peerDependenciesMeta: + typescript: + optional: true + + eslint-import-resolver-node@0.3.10: + resolution: {integrity: sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==} + + eslint-import-resolver-typescript@3.10.1: + resolution: {integrity: sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '*' + eslint-plugin-import: '*' + eslint-plugin-import-x: '*' + peerDependenciesMeta: + eslint-plugin-import: + optional: true + eslint-plugin-import-x: + optional: true + + eslint-module-utils@2.13.0: + resolution: {integrity: sha512-bLohSkT6469rRs8czj0tLTD8vaeIS/whvPRJVjDr7IuoTT1k5DYDERlNycjDj/HkOlvQdYurmfZ/g3fG5bgeLQ==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + + eslint-plugin-import@2.32.0: + resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + + eslint-plugin-jsx-a11y@6.10.2: + resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==} + engines: {node: '>=4.0'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 + + eslint-plugin-react-hooks@7.1.1: + resolution: {integrity: sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==} + engines: {node: '>=18'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0 + + eslint-plugin-react@7.37.5: + resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@9.39.4: + resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.1: + resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + generator-function@2.0.1: + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: '>= 0.4'} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + + get-tsconfig@4.14.0: + resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@16.4.0: + resolution: {integrity: sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==} + engines: {node: '>=18'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-bun-module@2.0.0: + resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.16.2: + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + + is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + iterator.prototype@1.1.5: + resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} + engines: {node: '>= 0.4'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.2.0: + resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsx-ast-utils@3.3.5: + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + language-subtag-registry@0.3.23: + resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} + + language-tags@1.0.9: + resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} + engines: {node: '>=0.10'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lucide-react@0.511.0: + resolution: {integrity: sha512-VK5a2ydJ7xm8GvBeKLS9mu1pVK6ucef9780JVUjw6bAjJL/QXnd4Y0p7SPeOUMC27YhzNCZvm5d/QX0Tp3rc0w==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + napi-postinstall@0.3.4: + resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + next@16.2.6: + resolution: {integrity: sha512-qOVgKJg1+At15NpeUP+eJgCHvTCgXsogweq87Ri/Ix7PkqQHg4sdaXmSFqKlgaIXE4kW0g25LE68W87UANlHtw==} + engines: {node: '>=20.9.0'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.51.1 + babel-plugin-react-compiler: '*' + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + + node-exports-info@1.6.0: + resolution: {integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==} + engines: {node: '>= 0.4'} + + node-releases@2.0.46: + resolution: {integrity: sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==} + engines: {node: '>=18'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + object.entries@1.1.9: + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.groupby@1.0.3: + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} + engines: {node: '>= 0.4'} + + object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + react-dom@19.2.6: + resolution: {integrity: sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==} + peerDependencies: + react: ^19.2.6 + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react@19.2.6: + resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==} + engines: {node: '>=0.10.0'} + + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + resolve@2.0.0-next.7: + resolution: {integrity: sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==} + engines: {node: '>= 0.4'} + hasBin: true + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-array-concat@1.1.4: + resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} + engines: {node: '>=0.4'} + + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.8.1: + resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==} + engines: {node: '>=10'} + hasBin: true + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + stable-hash@0.0.5: + resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} + + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} + + string.prototype.includes@2.0.1: + resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} + engines: {node: '>= 0.4'} + + string.prototype.matchall@4.0.12: + resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} + engines: {node: '>= 0.4'} + + string.prototype.repeat@1.0.0: + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} + + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + styled-jsx@5.1.6: + resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + tsconfig-paths@3.15.0: + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.8: + resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==} + engines: {node: '>= 0.4'} + + typescript-eslint@8.60.0: + resolution: {integrity: sha512-9f65qWLZdAW9m1JaxBDUHcqRUfL8bkxxXL7XxEfI+F09q56PkBvIfCjLF3yInsDM/BBmwkqmCQdCZe/RYlIWEw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + unrs-resolver@1.12.2: + resolution: {integrity: sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.21: + resolution: {integrity: sha512-zbRA8cVm6io/d5W8uIe2hblzN76/Wm3v/yiythQvr+dpBWeqhPSWIDNj4zOyHi4zKbMK6DN34Xsr9jPHJERAEw==} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zod-validation-error@4.0.2: + resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + +snapshots: + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.7': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.2 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/parser@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/traverse@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@emnapi/core@1.10.0': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.10.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4)': + dependencies: + eslint: 9.39.4 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.2': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.5 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.5': + dependencies: + ajv: 6.15.0 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.2.0 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.4': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 + + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 + + '@humanfs/types@0.15.0': {} + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@img/colour@1.1.0': + optional: true + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.10.0 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.2 + optional: true + + '@next/env@16.2.6': {} + + '@next/eslint-plugin-next@16.2.6': + dependencies: + fast-glob: 3.3.1 + + '@next/swc-darwin-arm64@16.2.6': + optional: true + + '@next/swc-darwin-x64@16.2.6': + optional: true + + '@next/swc-linux-arm64-gnu@16.2.6': + optional: true + + '@next/swc-linux-arm64-musl@16.2.6': + optional: true + + '@next/swc-linux-x64-gnu@16.2.6': + optional: true + + '@next/swc-linux-x64-musl@16.2.6': + optional: true + + '@next/swc-win32-arm64-msvc@16.2.6': + optional: true + + '@next/swc-win32-x64-msvc@16.2.6': + optional: true + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@nolyfill/is-core-module@1.0.39': {} + + '@rtsao/scc@1.1.0': {} + + '@swc/helpers@0.5.15': + dependencies: + tslib: 2.8.1 + + '@tybys/wasm-util@0.10.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/estree@1.0.9': {} + + '@types/json-schema@7.0.15': {} + + '@types/json5@0.0.29': {} + + '@types/node@22.19.19': + dependencies: + undici-types: 6.21.0 + + '@types/react-dom@19.2.3(@types/react@19.2.15)': + dependencies: + '@types/react': 19.2.15 + + '@types/react@19.2.15': + dependencies: + csstype: 3.2.3 + + '@typescript-eslint/eslint-plugin@8.60.0(@typescript-eslint/parser@8.60.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.60.0(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.60.0 + '@typescript-eslint/type-utils': 8.60.0(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/utils': 8.60.0(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.60.0 + eslint: 9.39.4 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.60.0(eslint@9.39.4)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.60.0 + '@typescript-eslint/types': 8.60.0 + '@typescript-eslint/typescript-estree': 8.60.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.60.0 + debug: 4.4.3 + eslint: 9.39.4 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.60.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.60.0(typescript@5.9.3) + '@typescript-eslint/types': 8.60.0 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.60.0': + dependencies: + '@typescript-eslint/types': 8.60.0 + '@typescript-eslint/visitor-keys': 8.60.0 + + '@typescript-eslint/tsconfig-utils@8.60.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.60.0(eslint@9.39.4)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.60.0 + '@typescript-eslint/typescript-estree': 8.60.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.60.0(eslint@9.39.4)(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.39.4 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.60.0': {} + + '@typescript-eslint/typescript-estree@8.60.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.60.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.60.0(typescript@5.9.3) + '@typescript-eslint/types': 8.60.0 + '@typescript-eslint/visitor-keys': 8.60.0 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.8.1 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.60.0(eslint@9.39.4)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4) + '@typescript-eslint/scope-manager': 8.60.0 + '@typescript-eslint/types': 8.60.0 + '@typescript-eslint/typescript-estree': 8.60.0(typescript@5.9.3) + eslint: 9.39.4 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.60.0': + dependencies: + '@typescript-eslint/types': 8.60.0 + eslint-visitor-keys: 5.0.1 + + '@unrs/resolver-binding-android-arm-eabi@1.12.2': + optional: true + + '@unrs/resolver-binding-android-arm64@1.12.2': + optional: true + + '@unrs/resolver-binding-darwin-arm64@1.12.2': + optional: true + + '@unrs/resolver-binding-darwin-x64@1.12.2': + optional: true + + '@unrs/resolver-binding-freebsd-x64@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-arm-musleabihf@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-arm64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-arm64-musl@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-loong64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-loong64-musl@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-ppc64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-riscv64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-riscv64-musl@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-s390x-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-x64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-x64-musl@1.12.2': + optional: true + + '@unrs/resolver-binding-openharmony-arm64@1.12.2': + optional: true + + '@unrs/resolver-binding-wasm32-wasi@1.12.2': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@unrs/resolver-binding-win32-arm64-msvc@1.12.2': + optional: true + + '@unrs/resolver-binding-win32-ia32-msvc@1.12.2': + optional: true + + '@unrs/resolver-binding-win32-x64-msvc@1.12.2': + optional: true + + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + ajv@6.15.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + argparse@2.0.1: {} + + aria-query@5.3.2: {} + + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + array-includes@3.1.9: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 + get-intrinsic: 1.3.0 + is-string: 1.1.1 + math-intrinsics: 1.1.0 + + array.prototype.findlast@1.2.5: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + es-shim-unscopables: 1.1.0 + + array.prototype.findlastindex@1.2.6: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + es-shim-unscopables: 1.1.0 + + array.prototype.flat@1.3.3: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-shim-unscopables: 1.1.0 + + array.prototype.flatmap@1.3.3: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-shim-unscopables: 1.1.0 + + array.prototype.tosorted@1.1.4: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-shim-unscopables: 1.1.0 + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + + ast-types-flow@0.0.8: {} + + async-function@1.0.0: {} + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + axe-core@4.11.4: {} + + axobject-query@4.1.0: {} + + balanced-match@1.0.2: {} + + balanced-match@4.0.4: {} + + baseline-browser-mapping@2.10.33: {} + + brace-expansion@1.1.15: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@5.0.6: + dependencies: + balanced-match: 4.0.4 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.2: + dependencies: + baseline-browser-mapping: 2.10.33 + caniuse-lite: 1.0.30001793 + electron-to-chromium: 1.5.364 + node-releases: 2.0.46 + update-browserslist-db: 1.2.3(browserslist@4.28.2) + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.9: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + caniuse-lite@1.0.30001793: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + client-only@0.0.1: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + concat-map@0.0.1: {} + + convert-source-map@2.0.0: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + csstype@3.2.3: {} + + damerau-levenshtein@1.0.8: {} + + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + debug@3.2.7: + dependencies: + ms: 2.1.3 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + detect-libc@2.1.2: + optional: true + + doctrine@2.1.0: + dependencies: + esutils: 2.0.3 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + electron-to-chromium@1.5.364: {} + + emoji-regex@9.2.2: {} + + es-abstract@1.24.2: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-negative-zero: 2.0.3 + is-regex: 1.2.1 + is-set: 2.0.3 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.4 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.8 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.21 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-iterator-helpers@1.3.2: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-set-tostringtag: 2.1.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + iterator.prototype: 1.1.5 + math-intrinsics: 1.1.0 + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.4 + + es-shim-unscopables@1.1.0: + dependencies: + hasown: 2.0.4 + + es-to-primitive@1.3.0: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-config-next@16.2.6(@typescript-eslint/parser@8.60.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3): + dependencies: + '@next/eslint-plugin-next': 16.2.6 + eslint: 9.39.4 + eslint-import-resolver-node: 0.3.10 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.60.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4))(eslint@9.39.4) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.60.0(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4) + eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.4) + eslint-plugin-react: 7.37.5(eslint@9.39.4) + eslint-plugin-react-hooks: 7.1.1(eslint@9.39.4) + globals: 16.4.0 + typescript-eslint: 8.60.0(eslint@9.39.4)(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - '@typescript-eslint/parser' + - eslint-import-resolver-webpack + - eslint-plugin-import-x + - supports-color + + eslint-import-resolver-node@0.3.10: + dependencies: + debug: 3.2.7 + is-core-module: 2.16.2 + resolve: 2.0.0-next.7 + transitivePeerDependencies: + - supports-color + + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.60.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4))(eslint@9.39.4): + dependencies: + '@nolyfill/is-core-module': 1.0.39 + debug: 4.4.3 + eslint: 9.39.4 + get-tsconfig: 4.14.0 + is-bun-module: 2.0.0 + stable-hash: 0.0.5 + tinyglobby: 0.2.17 + unrs-resolver: 1.12.2 + optionalDependencies: + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.60.0(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4) + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.13.0(@typescript-eslint/parser@8.60.0(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.60.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4))(eslint@9.39.4))(eslint@9.39.4): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 8.60.0(eslint@9.39.4)(typescript@5.9.3) + eslint: 9.39.4 + eslint-import-resolver-node: 0.3.10 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.60.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4))(eslint@9.39.4) + transitivePeerDependencies: + - supports-color + + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.60.0(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.9 + array.prototype.findlastindex: 1.2.6 + array.prototype.flat: 1.3.3 + array.prototype.flatmap: 1.3.3 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 9.39.4 + eslint-import-resolver-node: 0.3.10 + eslint-module-utils: 2.13.0(@typescript-eslint/parser@8.60.0(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.60.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4))(eslint@9.39.4))(eslint@9.39.4) + hasown: 2.0.4 + is-core-module: 2.16.2 + is-glob: 4.0.3 + minimatch: 3.1.5 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.1 + semver: 6.3.1 + string.prototype.trimend: 1.0.9 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 8.60.0(eslint@9.39.4)(typescript@5.9.3) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + + eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.4): + dependencies: + aria-query: 5.3.2 + array-includes: 3.1.9 + array.prototype.flatmap: 1.3.3 + ast-types-flow: 0.0.8 + axe-core: 4.11.4 + axobject-query: 4.1.0 + damerau-levenshtein: 1.0.8 + emoji-regex: 9.2.2 + eslint: 9.39.4 + hasown: 2.0.4 + jsx-ast-utils: 3.3.5 + language-tags: 1.0.9 + minimatch: 3.1.5 + object.fromentries: 2.0.8 + safe-regex-test: 1.1.0 + string.prototype.includes: 2.0.1 + + eslint-plugin-react-hooks@7.1.1(eslint@9.39.4): + dependencies: + '@babel/core': 7.29.7 + '@babel/parser': 7.29.7 + eslint: 9.39.4 + hermes-parser: 0.25.1 + zod: 4.4.3 + zod-validation-error: 4.0.2(zod@4.4.3) + transitivePeerDependencies: + - supports-color + + eslint-plugin-react@7.37.5(eslint@9.39.4): + dependencies: + array-includes: 3.1.9 + array.prototype.findlast: 1.2.5 + array.prototype.flatmap: 1.3.3 + array.prototype.tosorted: 1.1.4 + doctrine: 2.1.0 + es-iterator-helpers: 1.3.2 + eslint: 9.39.4 + estraverse: 5.3.0 + hasown: 2.0.4 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.5 + object.entries: 1.1.9 + object.fromentries: 2.0.8 + object.values: 1.2.1 + prop-types: 15.8.1 + resolve: 2.0.0-next.7 + semver: 6.3.1 + string.prototype.matchall: 4.0.12 + string.prototype.repeat: 1.0.0 + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@9.39.4: + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.2 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.5 + '@eslint/js': 9.39.4 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.8 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.9 + ajv: 6.15.0 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.1: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.4.2 + keyv: 4.5.4 + + flatted@3.4.2: {} + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + function-bind@1.1.2: {} + + function.prototype.name@1.1.8: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + functions-have-names: 1.2.3 + hasown: 2.0.4 + is-callable: 1.2.7 + + functions-have-names@1.2.3: {} + + generator-function@2.0.1: {} + + gensync@1.0.0-beta.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + + get-tsconfig@4.14.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@14.0.0: {} + + globals@16.4.0: {} + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + + gopd@1.2.0: {} + + has-bigints@1.1.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + hermes-estree@0.25.1: {} + + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.4 + side-channel: 1.1.0 + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-bun-module@2.0.0: + dependencies: + semver: 7.8.1 + + is-callable@1.2.7: {} + + is-core-module@2.16.2: + dependencies: + hasown: 2.0.4 + + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-generator-function@1.1.2: + dependencies: + call-bound: 1.0.4 + generator-function: 2.0.1 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-map@2.0.3: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-number@7.0.0: {} + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.4 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.21 + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + iterator.prototype@1.1.5: + dependencies: + define-data-property: 1.1.4 + es-object-atoms: 1.1.2 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + has-symbols: 1.1.0 + set-function-name: 2.0.2 + + js-tokens@4.0.0: {} + + js-yaml@4.2.0: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@1.0.2: + dependencies: + minimist: 1.2.8 + + json5@2.2.3: {} + + jsx-ast-utils@3.3.5: + dependencies: + array-includes: 3.1.9 + array.prototype.flat: 1.3.3 + object.assign: 4.1.7 + object.values: 1.2.1 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + language-subtag-registry@0.3.23: {} + + language-tags@1.0.9: + dependencies: + language-subtag-registry: 0.3.23 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lucide-react@0.511.0(react@19.2.6): + dependencies: + react: 19.2.6 + + math-intrinsics@1.1.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.6 + + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.15 + + minimist@1.2.8: {} + + ms@2.1.3: {} + + nanoid@3.3.12: {} + + napi-postinstall@0.3.4: {} + + natural-compare@1.4.0: {} + + next@16.2.6(@babel/core@7.29.7)(react-dom@19.2.6(react@19.2.6))(react@19.2.6): + dependencies: + '@next/env': 16.2.6 + '@swc/helpers': 0.5.15 + baseline-browser-mapping: 2.10.33 + caniuse-lite: 1.0.30001793 + postcss: 8.4.31 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + styled-jsx: 5.1.6(@babel/core@7.29.7)(react@19.2.6) + optionalDependencies: + '@next/swc-darwin-arm64': 16.2.6 + '@next/swc-darwin-x64': 16.2.6 + '@next/swc-linux-arm64-gnu': 16.2.6 + '@next/swc-linux-arm64-musl': 16.2.6 + '@next/swc-linux-x64-gnu': 16.2.6 + '@next/swc-linux-x64-musl': 16.2.6 + '@next/swc-win32-arm64-msvc': 16.2.6 + '@next/swc-win32-x64-msvc': 16.2.6 + sharp: 0.34.5 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + + node-exports-info@1.6.0: + dependencies: + array.prototype.flatmap: 1.3.3 + es-errors: 1.3.0 + object.entries: 1.1.9 + semver: 6.3.1 + + node-releases@2.0.46: {} + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.entries@1.1.9: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 + + object.groupby@1.0.3: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + + object.values@1.2.1: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.4: {} + + possible-typed-array-names@1.1.0: {} + + postcss@8.4.31: + dependencies: + nanoid: 3.3.12 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + punycode@2.3.1: {} + + queue-microtask@1.2.3: {} + + react-dom@19.2.6(react@19.2.6): + dependencies: + react: 19.2.6 + scheduler: 0.27.0 + + react-is@16.13.1: {} + + react@19.2.6: {} + + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + + resolve-from@4.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + resolve@2.0.0-next.7: + dependencies: + es-errors: 1.3.0 + is-core-module: 2.16.2 + node-exports-info: 1.6.0 + object-keys: 1.1.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.1.0: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-array-concat@1.1.4: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + scheduler@0.27.0: {} + + semver@6.3.1: {} + + semver@7.8.1: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + + sharp@0.34.5: + dependencies: + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.8.1 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + optional: true + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + source-map-js@1.2.1: {} + + stable-hash@0.0.5: {} + + stop-iteration-iterator@1.1.0: + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 + + string.prototype.includes@2.0.1: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + + string.prototype.matchall@4.0.12: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + regexp.prototype.flags: 1.5.4 + set-function-name: 2.0.2 + side-channel: 1.1.0 + + string.prototype.repeat@1.0.0: + dependencies: + define-properties: 1.2.1 + es-abstract: 1.24.2 + + string.prototype.trim@1.2.10: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 + has-property-descriptors: 1.0.2 + + string.prototype.trimend@1.0.9: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + + strip-bom@3.0.0: {} + + strip-json-comments@3.1.1: {} + + styled-jsx@5.1.6(@babel/core@7.29.7)(react@19.2.6): + dependencies: + client-only: 0.0.1 + react: 19.2.6 + optionalDependencies: + '@babel/core': 7.29.7 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + ts-api-utils@2.5.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + tsconfig-paths@3.15.0: + dependencies: + '@types/json5': 0.0.29 + json5: 1.0.2 + minimist: 1.2.8 + strip-bom: 3.0.0 + + tslib@2.8.1: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.8: + dependencies: + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + + typescript-eslint@8.60.0(eslint@9.39.4)(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.60.0(@typescript-eslint/parser@8.60.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/parser': 8.60.0(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.60.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.60.0(eslint@9.39.4)(typescript@5.9.3) + eslint: 9.39.4 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + typescript@5.9.3: {} + + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + + undici-types@6.21.0: {} + + unrs-resolver@1.12.2: + dependencies: + napi-postinstall: 0.3.4 + optionalDependencies: + '@unrs/resolver-binding-android-arm-eabi': 1.12.2 + '@unrs/resolver-binding-android-arm64': 1.12.2 + '@unrs/resolver-binding-darwin-arm64': 1.12.2 + '@unrs/resolver-binding-darwin-x64': 1.12.2 + '@unrs/resolver-binding-freebsd-x64': 1.12.2 + '@unrs/resolver-binding-linux-arm-gnueabihf': 1.12.2 + '@unrs/resolver-binding-linux-arm-musleabihf': 1.12.2 + '@unrs/resolver-binding-linux-arm64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-arm64-musl': 1.12.2 + '@unrs/resolver-binding-linux-loong64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-loong64-musl': 1.12.2 + '@unrs/resolver-binding-linux-ppc64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-riscv64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-riscv64-musl': 1.12.2 + '@unrs/resolver-binding-linux-s390x-gnu': 1.12.2 + '@unrs/resolver-binding-linux-x64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-x64-musl': 1.12.2 + '@unrs/resolver-binding-openharmony-arm64': 1.12.2 + '@unrs/resolver-binding-wasm32-wasi': 1.12.2 + '@unrs/resolver-binding-win32-arm64-msvc': 1.12.2 + '@unrs/resolver-binding-win32-ia32-msvc': 1.12.2 + '@unrs/resolver-binding-win32-x64-msvc': 1.12.2 + + update-browserslist-db@1.2.3(browserslist@4.28.2): + dependencies: + browserslist: 4.28.2 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.2 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.21 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.21: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + yallist@3.1.1: {} + + yocto-queue@0.1.0: {} + + zod-validation-error@4.0.2(zod@4.4.3): + dependencies: + zod: 4.4.3 + + zod@4.4.3: {} diff --git a/public/icon.svg b/public/icon.svg new file mode 100644 index 0000000..bde4fbb --- /dev/null +++ b/public/icon.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/public/sw.js b/public/sw.js new file mode 100644 index 0000000..c385644 --- /dev/null +++ b/public/sw.js @@ -0,0 +1,7 @@ +self.addEventListener("install", function () { + self.skipWaiting(); +}); + +self.addEventListener("activate", function (event) { + event.waitUntil(self.clients.claim()); +}); diff --git a/src/app/(app)/announcements/[id]/page.tsx b/src/app/(app)/announcements/[id]/page.tsx new file mode 100644 index 0000000..b4c43f9 --- /dev/null +++ b/src/app/(app)/announcements/[id]/page.tsx @@ -0,0 +1,68 @@ +import Link from "next/link"; +import { notFound } from "next/navigation"; +import { ArrowLeft, Bell } from "lucide-react"; + +import { AnnouncementReadMarker } from "@/components/announcement-read-marker"; +import { PageHeader } from "@/components/page-header"; +import { StatusPill } from "@/components/status-pill"; +import { BackendError } from "@/lib/backend"; +import { formatDateTime } from "@/lib/format"; +import { getAnnouncementDetail } from "@/lib/mobile-data"; +import type { AnnouncementSummary } from "@/lib/types"; + +type AnnouncementDetailPageProps = { + params: Promise<{ id: string }>; +}; + +const levelText: Record = { + normal: "普通", + important: "重要", + urgent: "紧急" +}; + +const levelTone: Record = { + normal: "default", + important: "warning", + urgent: "danger" +}; + +export default async function AnnouncementDetailPage({ params }: AnnouncementDetailPageProps) { + const { id } = await params; + let announcement: Awaited>; + + try { + announcement = await getAnnouncementDetail(id); + } catch (error) { + if (error instanceof BackendError && error.status === 404) { + notFound(); + } + + throw error; + } + + return ( +
+ + + 返回公告 + + + +
+
+ + + 公告级别 + + {levelText[announcement.level]} + {announcement.read ? "已读" : "未读"} +
+
{announcement.content}
+
+
+ ); +} diff --git a/src/app/(app)/announcements/page.tsx b/src/app/(app)/announcements/page.tsx new file mode 100644 index 0000000..56760df --- /dev/null +++ b/src/app/(app)/announcements/page.tsx @@ -0,0 +1,92 @@ +import Link from "next/link"; +import type { Route } from "next"; + +import { EmptyState } from "@/components/empty-state"; +import { FilterNav } from "@/components/filter-nav"; +import { PageHeader } from "@/components/page-header"; +import { StatusPill } from "@/components/status-pill"; +import { formatDateTime } from "@/lib/format"; +import { getAnnouncements } from "@/lib/mobile-data"; + +type AnnouncementFilter = "all" | "unread" | "read"; + +type AnnouncementsPageProps = { + searchParams: Promise<{ read?: string | string[] }>; +}; + +function firstSearchValue(value: string | string[] | undefined) { + return Array.isArray(value) ? value[0] : value; +} + +function normalizeFilter(value: string | string[] | undefined): AnnouncementFilter { + const read = firstSearchValue(value); + if (read === "unread" || read === "read") return read; + return "all"; +} + +function emptyCopy(filter: AnnouncementFilter) { + if (filter === "unread") { + return { + title: "暂无未读公告", + description: "当前没有需要你阅读的新公告。" + }; + } + + if (filter === "read") { + return { + title: "暂无已读公告", + description: "你阅读过的公告会保留在这里,方便回看。" + }; + } + + return { + title: "暂无公告", + description: "当前没有面向你的门店公告。" + }; +} + +export default async function AnnouncementsPage({ searchParams }: AnnouncementsPageProps) { + const filter = normalizeFilter((await searchParams).read); + const allAnnouncements = await getAnnouncements(); + const announcements = allAnnouncements.filter((item) => { + if (filter === "unread") return !item.read; + if (filter === "read") return item.read; + return true; + }); + const empty = emptyCopy(filter); + + return ( +
+ + + {announcements.length > 0 ? ( +
+ {announcements.map((item) => { + const href = `/announcements/${item.id}` as Route; + + return ( + +
+

{item.title}

+

{item.summary || formatDateTime(item.publishedAt)}

+
+ + {item.read ? "已读" : "未读"} + + + ); + })} +
+ ) : ( + + )} +
+ ); +} diff --git a/src/app/(app)/dashboard/page.tsx b/src/app/(app)/dashboard/page.tsx new file mode 100644 index 0000000..9f20f1c --- /dev/null +++ b/src/app/(app)/dashboard/page.tsx @@ -0,0 +1,168 @@ +import Link from "next/link"; +import type { Route } from "next"; +import { Bell, CalendarDays, ChevronRight, ClipboardList } from "lucide-react"; + +import { EmptyState } from "@/components/empty-state"; +import { StatusPill } from "@/components/status-pill"; +import { formatDateTime, roleNames } from "@/lib/format"; +import { getBootstrapData } from "@/lib/mobile-data"; +import type { TaskStatus } from "@/lib/types"; + +const taskStatusText: Record = { + pending: "待处理", + in_progress: "处理中", + completed: "已完成", + cancelled: "已取消" +}; + +const taskStatusTone: Record = { + pending: "default", + in_progress: "warning", + completed: "success", + cancelled: "danger" +}; + +export default async function DashboardPage() { + const data = await getBootstrapData(); + const { user } = data; + + return ( +
+
+
+

{user.storeName || "员工端"}

+

你好,{user.displayName}

+

今日工作状态已同步,先处理最重要的事项。

+
+
+ + {data.pendingTaskCount} + 待办 + + + {data.todayShifts.length} + 班次 + + + {data.unreadAnnouncementCount} + 未读 + +
+
+ +
+
+ {user.displayName.slice(0, 1)} +
+
+

{user.displayName}

+

{user.storeName || "未绑定门店"}

+
+ 账号正常 + {roleNames(user.roles)} +
+
+
+ +
+ + + {data.pendingTaskCount} +

待办任务

+ + + + {data.overdueTaskCount} +

已逾期

+ + + + {data.unreadAnnouncementCount} +

未读公告

+ +
+ +
+
+

今日排班

+ +
+ {data.todayShifts.length > 0 ? ( +
+ {data.todayShifts.map((shift) => ( +
+
+

{shift.position}

+

+ {formatDateTime(shift.startAt)} - {formatDateTime(shift.endAt)} +

+
+ 已排班 +
+ ))} +
+ ) : ( + + )} +
+ +
+
+

待办任务

+ + 查看全部 + + +
+ {data.tasks.length > 0 ? ( +
+ {data.tasks.map((task) => { + const href = `/tasks/${task.id}` as Route; + + return ( + +
+

{task.title}

+

{task.description || `截止:${formatDateTime(task.dueAt)}`}

+
+ {taskStatusText[task.status]} + + ); + })} +
+ ) : ( + + )} +
+ +
+
+

最新公告

+ + 查看全部 + + +
+ {data.latestAnnouncements.length > 0 ? ( +
+ {data.latestAnnouncements.map((item) => { + const href = `/announcements/${item.id}` as Route; + + return ( + +
+

{item.title}

+

{item.summary || formatDateTime(item.publishedAt)}

+
+ {item.read ? "已读" : "未读"} + + ); + })} +
+ ) : ( + + )} +
+
+ ); +} diff --git a/src/app/(app)/layout.tsx b/src/app/(app)/layout.tsx new file mode 100644 index 0000000..6822396 --- /dev/null +++ b/src/app/(app)/layout.tsx @@ -0,0 +1,10 @@ +import { BottomNav } from "@/components/bottom-nav"; + +export default function AppLayout({ children }: Readonly<{ children: React.ReactNode }>) { + return ( +
+
{children}
+ +
+ ); +} diff --git a/src/app/(app)/loading.tsx b/src/app/(app)/loading.tsx new file mode 100644 index 0000000..ae1b97b --- /dev/null +++ b/src/app/(app)/loading.tsx @@ -0,0 +1,10 @@ +export default function Loading() { + return ( +
+
+
+
+
+
+ ); +} diff --git a/src/app/(app)/me/page.tsx b/src/app/(app)/me/page.tsx new file mode 100644 index 0000000..8907c8a --- /dev/null +++ b/src/app/(app)/me/page.tsx @@ -0,0 +1,52 @@ +import { KeyRound, ShieldCheck, UserRound } from "lucide-react"; + +import { LogoutButton } from "@/components/logout-button"; +import { PageHeader } from "@/components/page-header"; +import { PasswordChangeForm } from "@/components/password-change-form"; +import { StatusPill } from "@/components/status-pill"; +import { roleNames } from "@/lib/format"; +import { getCurrentUser } from "@/lib/mobile-data"; + +export default async function MePage() { + const user = await getCurrentUser(); + + return ( +
+ +
+ {user.displayName.slice(0, 1)} +

{user.displayName}

+

{user.username}

+
+ 员工账号 + {roleNames(user.roles)} +
+
+
+
+ + 所属门店 + {user.storeName || "未绑定门店"} +
+
+ + 权限数量 + {user.permissions.includes("*") ? "全部权限" : `${user.permissions.length} 项`} +
+
+ + 修改密码 + 旧密码 + 新密码 +
+
+
+
+

账号安全

+
+

本应用不展示明文密码,也不会在前端持久化 token 或密码。

+ +
+ +
+ ); +} diff --git a/src/app/(app)/schedule/page.tsx b/src/app/(app)/schedule/page.tsx new file mode 100644 index 0000000..ecdab19 --- /dev/null +++ b/src/app/(app)/schedule/page.tsx @@ -0,0 +1,87 @@ +import { EmptyState } from "@/components/empty-state"; +import { FilterNav } from "@/components/filter-nav"; +import { PageHeader } from "@/components/page-header"; +import { StatusPill } from "@/components/status-pill"; +import { formatDateTime } from "@/lib/format"; +import { getShifts } from "@/lib/mobile-data"; +import type { Route } from "next"; + +type ScheduleRange = "today" | "upcoming" | "all"; + +type SchedulePageProps = { + searchParams: Promise<{ range?: string | string[] }>; +}; + +const statusText = { + scheduled: "已排班", + cancelled: "已取消", + completed: "已完成" +} as const; + +function firstSearchValue(value: string | string[] | undefined) { + return Array.isArray(value) ? value[0] : value; +} + +function toDateString(date: Date) { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, "0"); + const day = String(date.getDate()).padStart(2, "0"); + return `${year}-${month}-${day}`; +} + +function normalizeRange(value: string | string[] | undefined): ScheduleRange { + const range = firstSearchValue(value); + if (range === "upcoming" || range === "all") return range; + return "today"; +} + +function rangeFilter(range: ScheduleRange) { + const today = toDateString(new Date()); + if (range === "today") return { startDate: today, endDate: today }; + if (range === "upcoming") return { startDate: today }; + return {}; +} + +function emptyCopy(range: ScheduleRange) { + if (range === "today") return "今天没有安排班次。"; + if (range === "upcoming") return "当前没有未来班次。"; + return "当前没有你的排班记录。"; +} + +export default async function SchedulePage({ searchParams }: SchedulePageProps) { + const range = normalizeRange((await searchParams).range); + const shifts = await getShifts(rangeFilter(range)); + + return ( +
+ + + {shifts.length > 0 ? ( +
+ {shifts.map((shift) => ( +
+
+

{shift.position}

+

+ {formatDateTime(shift.startAt)} - {formatDateTime(shift.endAt)} +

+
+ + {statusText[shift.status]} + +
+ ))} +
+ ) : ( + + )} +
+ ); +} diff --git a/src/app/(app)/store/page.tsx b/src/app/(app)/store/page.tsx new file mode 100644 index 0000000..ef9d742 --- /dev/null +++ b/src/app/(app)/store/page.tsx @@ -0,0 +1,71 @@ +import { MapPin, Phone, Store } from "lucide-react"; + +import { EmptyState } from "@/components/empty-state"; +import { PageHeader } from "@/components/page-header"; +import { StatusPill } from "@/components/status-pill"; +import { getCurrentStore, getCurrentStoreEmployees, getCurrentUser, getPermissionPayload } from "@/lib/mobile-data"; + +export default async function StorePage() { + const [user, permissions] = await Promise.all([getCurrentUser(), getPermissionPayload()]); + const canViewStoreEmployees = + permissions.permissions.includes("*") || + permissions.permissions.includes("employee:view:store") || + permissions.permissions.includes("employee:view:all"); + const [store, employees] = await Promise.all([ + getCurrentStore(user), + canViewStoreEmployees ? getCurrentStoreEmployees(user) : Promise.resolve([]) + ]); + const storeName = store?.name || user.storeName || "未绑定门店"; + + return ( +
+ +
+ +
+

{storeName}

+

门店 ID:{store?.id ?? user.storeId ?? "暂无"}

+
+ + {store?.status === "INACTIVE" ? "停用" : "正常"} + +
+
+
+ + 地址 + {store?.address || "暂无"} +
+
+ + 电话 + {store?.phone || "暂无"} +
+
+ {canViewStoreEmployees && employees.length > 0 ? ( +
+
+

本店员工

+
+
+ {employees.map((employee) => ( +
+
+

{employee.name}

+

{employee.phone || employee.roles.map((role) => role.name).join("、") || "未分配角色"}

+
+ + {employee.status === "INACTIVE" ? "停用" : "在职"} + +
+ ))} +
+
+ ) : canViewStoreEmployees ? ( + + ) : ( + + )} +
+ ); +} diff --git a/src/app/(app)/tasks/[id]/page.tsx b/src/app/(app)/tasks/[id]/page.tsx new file mode 100644 index 0000000..06b24cc --- /dev/null +++ b/src/app/(app)/tasks/[id]/page.tsx @@ -0,0 +1,114 @@ +import Link from "next/link"; +import { notFound } from "next/navigation"; +import { ArrowLeft, CalendarClock, ClipboardList, Store, UsersRound } from "lucide-react"; + +import { PageHeader } from "@/components/page-header"; +import { StatusPill } from "@/components/status-pill"; +import { TaskActionPanel } from "@/components/task-action-panel"; +import { BackendError } from "@/lib/backend"; +import { formatDateTime } from "@/lib/format"; +import { getTaskDetail } from "@/lib/mobile-data"; +import type { TaskEvent, TaskStatus } from "@/lib/types"; + +type TaskDetailPageProps = { + params: Promise<{ id: string }>; +}; + +const statusText: Record = { + pending: "待处理", + in_progress: "处理中", + completed: "已完成", + cancelled: "已取消" +}; + +const statusTone: Record = { + pending: "warning", + in_progress: "warning", + completed: "success", + cancelled: "danger" +}; + +const eventText: Record = { + CREATED: "创建任务", + UPDATED: "更新任务", + STARTED: "开始处理", + COMPLETED: "完成任务", + CANCELLED: "取消任务", + COMMENTED: "追加备注" +}; + +function eventDescription(event: TaskEvent) { + const label = eventText[event.eventType] ?? event.eventType; + if (event.comment) return `${label}:${event.comment}`; + if (event.fromStatus && event.toStatus) return `${label}:${event.fromStatus} -> ${event.toStatus}`; + return label; +} + +export default async function TaskDetailPage({ params }: TaskDetailPageProps) { + const { id } = await params; + let task: Awaited>; + + try { + task = await getTaskDetail(id); + } catch (error) { + if (error instanceof BackendError && error.status === 404) { + notFound(); + } + + throw error; + } + + return ( +
+ + + 返回任务 + + +
+
+ + + 当前状态 + + {statusText[task.status]} +
+
+
+ + 截止时间 + {formatDateTime(task.dueAt)} +
+
+ + 所属门店 + {task.storeName || "未指定"} +
+
+ + 处理人 + {task.assignees.map((item) => item.name).join("、") || task.assigneeName || "未指定"} +
+
+
+ +
+
+

处理记录

+
+ {task.events.length > 0 ? ( +
+ {task.events.map((event) => ( +
+ {eventDescription(event)} + {formatDateTime(event.createdAt)} +
+ ))} +
+ ) : ( +

暂无处理记录。

+ )} +
+
+ ); +} diff --git a/src/app/(app)/tasks/page.tsx b/src/app/(app)/tasks/page.tsx new file mode 100644 index 0000000..26db34f --- /dev/null +++ b/src/app/(app)/tasks/page.tsx @@ -0,0 +1,88 @@ +import Link from "next/link"; +import type { Route } from "next"; + +import { EmptyState } from "@/components/empty-state"; +import { FilterNav } from "@/components/filter-nav"; +import { PageHeader } from "@/components/page-header"; +import { StatusPill } from "@/components/status-pill"; +import { formatDateTime } from "@/lib/format"; +import { getTasks } from "@/lib/mobile-data"; +import type { TaskStatus } from "@/lib/types"; + +const statusText: Record = { + pending: "待处理", + in_progress: "处理中", + completed: "已完成", + cancelled: "已取消" +}; + +const statusTone: Record = { + pending: "warning", + in_progress: "warning", + completed: "success", + cancelled: "danger" +}; + +type TasksPageProps = { + searchParams: Promise<{ status?: string | string[] }>; +}; + +function firstSearchValue(value: string | string[] | undefined) { + return Array.isArray(value) ? value[0] : value; +} + +function normalizeStatus(value: string | string[] | undefined): TaskStatus | "all" { + const status = firstSearchValue(value); + if (status === "pending" || status === "in_progress" || status === "completed" || status === "cancelled") { + return status; + } + + return "all"; +} + +function emptyCopy(status: TaskStatus | "all") { + if (status === "pending") return "当前没有待处理任务。"; + if (status === "in_progress") return "当前没有处理中的任务。"; + if (status === "completed") return "当前没有已完成任务。"; + if (status === "cancelled") return "当前没有已取消任务。"; + return "当前没有分配给你的任务。"; +} + +export default async function TasksPage({ searchParams }: TasksPageProps) { + const status = normalizeStatus((await searchParams).status); + const tasks = await getTasks(status === "all" ? {} : { status }); + + return ( +
+ + + {tasks.length > 0 ? ( +
+ {tasks.map((task) => { + const href = `/tasks/${task.id}` as Route; + + return ( + +
+

{task.title}

+

{task.description || `截止:${formatDateTime(task.dueAt)}`}

+
+ {statusText[task.status]} + + ); + })} +
+ ) : ( + + )} +
+ ); +} diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx new file mode 100644 index 0000000..86c4a66 --- /dev/null +++ b/src/app/(auth)/login/page.tsx @@ -0,0 +1,39 @@ +import { LoginForm } from "@/components/login-form"; + +export default function LoginPage() { + return ( +
+
+
+ + Role User +
+

Employee Console

+

门店工作,清晰处理

+

排班、任务、公告和账号安全集中在同一个移动端工作区。

+
+
+ + +
+
+ 08:30 + 早班 · 正常 +
+
+ + + +
+
+
+
+
+

员工登录

+

使用手机号和临时密码进入工作台。

+
+ +
+
+ ); +} diff --git a/src/app/api/auth/login/route.ts b/src/app/api/auth/login/route.ts new file mode 100644 index 0000000..3f4f6ee --- /dev/null +++ b/src/app/api/auth/login/route.ts @@ -0,0 +1,33 @@ +import { setSessionToken } from "@/lib/session"; +import { BackendError, backendRequest } from "@/lib/backend"; +import type { LoginResponse } from "@/lib/types"; + +export async function POST(request: Request) { + try { + const body = await request.json(); + const result = await backendRequest("/auth/employee/login", { + method: "POST", + body: JSON.stringify({ + username: body.username, + password: body.password + }), + token: "" + }); + + await setSessionToken(result.token); + + return Response.json({ + success: true, + data: { + user: result.user, + tokenType: result.tokenType, + expiresIn: result.expiresIn + } + }); + } catch (error) { + const status = error instanceof BackendError ? error.status : 500; + const message = error instanceof Error ? error.message : "登录失败"; + + return Response.json({ success: false, data: null, message }, { status }); + } +} diff --git a/src/app/api/auth/logout/route.ts b/src/app/api/auth/logout/route.ts new file mode 100644 index 0000000..825263a --- /dev/null +++ b/src/app/api/auth/logout/route.ts @@ -0,0 +1,15 @@ +import { NextRequest, NextResponse } from "next/server"; + +import { clearSessionToken } from "@/lib/session"; + +export async function POST() { + await clearSessionToken(); + return Response.json({ success: true, data: null }); +} + +export async function GET(request: NextRequest) { + await clearSessionToken(); + const next = request.nextUrl.searchParams.get("next") || "/login"; + + return NextResponse.redirect(new URL(next, request.url)); +} diff --git a/src/app/api/auth/me/password/route.ts b/src/app/api/auth/me/password/route.ts new file mode 100644 index 0000000..d581bd7 --- /dev/null +++ b/src/app/api/auth/me/password/route.ts @@ -0,0 +1,11 @@ +import { proxyBackendJson } from "@/lib/backend"; +import type { AuthUser } from "@/lib/types"; + +export async function PATCH(request: Request) { + const body = await request.text(); + + return proxyBackendJson("/auth/me/password", { + method: "PATCH", + body: body || undefined + }); +} diff --git a/src/app/api/auth/me/route.ts b/src/app/api/auth/me/route.ts new file mode 100644 index 0000000..2c5e9e3 --- /dev/null +++ b/src/app/api/auth/me/route.ts @@ -0,0 +1,6 @@ +import { proxyBackendJson } from "@/lib/backend"; +import type { AuthUser } from "@/lib/types"; + +export async function GET() { + return proxyBackendJson("/auth/me"); +} diff --git a/src/app/api/mobile/announcements/[id]/read/route.ts b/src/app/api/mobile/announcements/[id]/read/route.ts new file mode 100644 index 0000000..8d53e32 --- /dev/null +++ b/src/app/api/mobile/announcements/[id]/read/route.ts @@ -0,0 +1,13 @@ +import { proxyBackendJson } from "@/lib/backend"; +import type { AnnouncementDetail } from "@/lib/types"; + +type Params = { + params: Promise<{ id: string }>; +}; + +export async function POST(_request: Request, { params }: Params) { + const { id } = await params; + return proxyBackendJson(`/mobile/announcements/${encodeURIComponent(id)}/read`, { + method: "POST" + }); +} diff --git a/src/app/api/mobile/announcements/[id]/route.ts b/src/app/api/mobile/announcements/[id]/route.ts new file mode 100644 index 0000000..7e49c7f --- /dev/null +++ b/src/app/api/mobile/announcements/[id]/route.ts @@ -0,0 +1,11 @@ +import { proxyBackendJson } from "@/lib/backend"; +import type { AnnouncementDetail } from "@/lib/types"; + +type Params = { + params: Promise<{ id: string }>; +}; + +export async function GET(_request: Request, { params }: Params) { + const { id } = await params; + return proxyBackendJson(`/mobile/announcements/${encodeURIComponent(id)}`); +} diff --git a/src/app/api/mobile/announcements/route.ts b/src/app/api/mobile/announcements/route.ts new file mode 100644 index 0000000..57d9061 --- /dev/null +++ b/src/app/api/mobile/announcements/route.ts @@ -0,0 +1,6 @@ +import { proxyBackendJson } from "@/lib/backend"; + +export async function GET(request: Request) { + const search = new URL(request.url).search; + return proxyBackendJson(`/mobile/announcements${search}`); +} diff --git a/src/app/api/mobile/shifts/route.ts b/src/app/api/mobile/shifts/route.ts new file mode 100644 index 0000000..e103b74 --- /dev/null +++ b/src/app/api/mobile/shifts/route.ts @@ -0,0 +1,7 @@ +import { proxyBackendJson } from "@/lib/backend"; +import type { ShiftSummary } from "@/lib/types"; + +export async function GET(request: Request) { + const search = new URL(request.url).search; + return proxyBackendJson(`/mobile/shifts${search}`); +} diff --git a/src/app/api/mobile/shifts/today/route.ts b/src/app/api/mobile/shifts/today/route.ts new file mode 100644 index 0000000..482aab3 --- /dev/null +++ b/src/app/api/mobile/shifts/today/route.ts @@ -0,0 +1,6 @@ +import { proxyBackendJson } from "@/lib/backend"; +import type { ShiftSummary } from "@/lib/types"; + +export async function GET() { + return proxyBackendJson("/mobile/shifts/today"); +} diff --git a/src/app/api/mobile/store/employees/route.ts b/src/app/api/mobile/store/employees/route.ts new file mode 100644 index 0000000..cb9f4e5 --- /dev/null +++ b/src/app/api/mobile/store/employees/route.ts @@ -0,0 +1,31 @@ +import { backendErrorResponse, BackendError, backendRequest } from "@/lib/backend"; +import type { ApiEnvelope, AuthUser, StoreEmployee } from "@/lib/types"; + +export async function GET() { + try { + const user = await backendRequest("/auth/me"); + + if (!user.storeId) { + return Response.json({ success: true, data: [] } satisfies ApiEnvelope); + } + + const search = new URLSearchParams({ + storeId: String(user.storeId), + page: "1", + pageSize: "100" + }); + + try { + const employees = await backendRequest(`/employees?${search.toString()}`); + return Response.json({ success: true, data: employees } satisfies ApiEnvelope); + } catch (error) { + if (error instanceof BackendError && [403, 404].includes(error.status)) { + return Response.json({ success: true, data: [] } satisfies ApiEnvelope); + } + + throw error; + } + } catch (error) { + return backendErrorResponse(error); + } +} diff --git a/src/app/api/mobile/store/route.ts b/src/app/api/mobile/store/route.ts new file mode 100644 index 0000000..9a8c867 --- /dev/null +++ b/src/app/api/mobile/store/route.ts @@ -0,0 +1,38 @@ +import { backendErrorResponse, BackendError, backendRequest } from "@/lib/backend"; +import type { ApiEnvelope, AuthUser, StoreDetail } from "@/lib/types"; + +function fallbackStore(user: AuthUser): StoreDetail | null { + if (!user.storeId) return null; + + return { + id: user.storeId, + name: user.storeName ?? "当前门店", + address: null, + phone: null, + status: undefined, + employees: [] + }; +} + +export async function GET() { + try { + const user = await backendRequest("/auth/me"); + + if (!user.storeId) { + return Response.json({ success: true, data: null } satisfies ApiEnvelope); + } + + try { + const store = await backendRequest(`/stores/${user.storeId}`); + return Response.json({ success: true, data: store } satisfies ApiEnvelope); + } catch (error) { + if (error instanceof BackendError && [403, 404].includes(error.status)) { + return Response.json({ success: true, data: fallbackStore(user) } satisfies ApiEnvelope); + } + + throw error; + } + } catch (error) { + return backendErrorResponse(error); + } +} diff --git a/src/app/api/mobile/tasks/[id]/comment/route.ts b/src/app/api/mobile/tasks/[id]/comment/route.ts new file mode 100644 index 0000000..f141bc0 --- /dev/null +++ b/src/app/api/mobile/tasks/[id]/comment/route.ts @@ -0,0 +1,16 @@ +import { proxyBackendJson } from "@/lib/backend"; +import type { TaskDetail } from "@/lib/types"; + +type Params = { + params: Promise<{ id: string }>; +}; + +export async function POST(request: Request, { params }: Params) { + const { id } = await params; + const body = await request.text(); + + return proxyBackendJson(`/mobile/tasks/${encodeURIComponent(id)}/comment`, { + method: "POST", + body: body || undefined + }); +} diff --git a/src/app/api/mobile/tasks/[id]/complete/route.ts b/src/app/api/mobile/tasks/[id]/complete/route.ts new file mode 100644 index 0000000..44a7fd0 --- /dev/null +++ b/src/app/api/mobile/tasks/[id]/complete/route.ts @@ -0,0 +1,13 @@ +import { proxyBackendJson } from "@/lib/backend"; +import type { TaskDetail } from "@/lib/types"; + +type Params = { + params: Promise<{ id: string }>; +}; + +export async function POST(_request: Request, { params }: Params) { + const { id } = await params; + return proxyBackendJson(`/mobile/tasks/${encodeURIComponent(id)}/complete`, { + method: "POST" + }); +} diff --git a/src/app/api/mobile/tasks/[id]/route.ts b/src/app/api/mobile/tasks/[id]/route.ts new file mode 100644 index 0000000..9522ab1 --- /dev/null +++ b/src/app/api/mobile/tasks/[id]/route.ts @@ -0,0 +1,11 @@ +import { proxyBackendJson } from "@/lib/backend"; +import type { TaskDetail } from "@/lib/types"; + +type Params = { + params: Promise<{ id: string }>; +}; + +export async function GET(_request: Request, { params }: Params) { + const { id } = await params; + return proxyBackendJson(`/mobile/tasks/${encodeURIComponent(id)}`); +} diff --git a/src/app/api/mobile/tasks/[id]/start/route.ts b/src/app/api/mobile/tasks/[id]/start/route.ts new file mode 100644 index 0000000..04e96f4 --- /dev/null +++ b/src/app/api/mobile/tasks/[id]/start/route.ts @@ -0,0 +1,13 @@ +import { proxyBackendJson } from "@/lib/backend"; +import type { TaskDetail } from "@/lib/types"; + +type Params = { + params: Promise<{ id: string }>; +}; + +export async function POST(_request: Request, { params }: Params) { + const { id } = await params; + return proxyBackendJson(`/mobile/tasks/${encodeURIComponent(id)}/start`, { + method: "POST" + }); +} diff --git a/src/app/api/mobile/tasks/route.ts b/src/app/api/mobile/tasks/route.ts new file mode 100644 index 0000000..4cf4202 --- /dev/null +++ b/src/app/api/mobile/tasks/route.ts @@ -0,0 +1,6 @@ +import { proxyBackendJson } from "@/lib/backend"; + +export async function GET(request: Request) { + const search = new URL(request.url).search; + return proxyBackendJson(`/mobile/tasks${search}`); +} diff --git a/src/app/api/permissions/me/route.ts b/src/app/api/permissions/me/route.ts new file mode 100644 index 0000000..6132bcb --- /dev/null +++ b/src/app/api/permissions/me/route.ts @@ -0,0 +1,6 @@ +import { proxyBackendJson } from "@/lib/backend"; +import type { PermissionPayload } from "@/lib/types"; + +export async function GET() { + return proxyBackendJson("/permissions/me"); +} diff --git a/src/app/globals.css b/src/app/globals.css new file mode 100644 index 0000000..03439ee --- /dev/null +++ b/src/app/globals.css @@ -0,0 +1,1109 @@ +:root { + color-scheme: light; + --background: #ffffff; + --foreground: #0a0a0a; + --foreground-soft: rgba(0, 0, 0, 0.72); + --muted: rgba(0, 0, 0, 0.5); + --muted-strong: rgba(0, 0, 0, 0.64); + --page: #f4f4f6; + --surface: #ffffff; + --surface-muted: #f5f5f5; + --surface-subtle: #fafafa; + --accent: #000000; + --accent-soft: rgba(0, 0, 0, 0.06); + --accent-ink: #ffffff; + --border: rgba(0, 0, 0, 0.08); + --border-strong: rgba(0, 0, 0, 0.14); + --ring: rgba(0, 0, 0, 0.28); + --success: #17734f; + --success-bg: #eef8f3; + --warning: #9a5d0a; + --warning-bg: #fff8ea; + --danger: #b42318; + --danger-bg: #fff1ef; + --radius-button: 4px; + --radius-input: 4px; + --radius-control: 7px; + --radius-card: 12px; + --shadow-panel: 0 1px 3px rgba(0, 0, 0, 0.04), 0 4px 20px rgba(0, 0, 0, 0.04); + --shadow-floating: 0 12px 34px rgba(0, 0, 0, 0.065), 0 1px 2px rgba(0, 0, 0, 0.035); + --font-sans: "Inter", "PingFang SC", "Helvetica Neue", "Microsoft YaHei", Arial, sans-serif; +} + +* { + box-sizing: border-box; +} + +html, +body { + margin: 0; + min-height: 100%; +} + +html { + background: var(--page); +} + +body { + background: var(--page); + color: var(--foreground); + font-family: var(--font-sans); + font-variant-numeric: tabular-nums; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-rendering: optimizeLegibility; +} + +a { + color: inherit; + text-decoration: none; +} + +button, +input, +textarea { + font: inherit; +} + +button { + cursor: pointer; +} + +button:focus-visible, +a:focus-visible, +input:focus-visible, +textarea:focus-visible { + outline: 2px solid var(--ring); + outline-offset: 2px; +} + +.app-shell { + min-height: 100vh; +} + +.app-main { + margin: 0 auto; + max-width: 480px; + min-height: 100vh; + padding: 14px 14px 104px; +} + +.page-stack { + display: flex; + flex-direction: column; + gap: 12px; +} + +.page-header, +.dashboard-hero, +.identity-card, +.profile-card, +.store-panel, +.section-block, +.login-panel, +.detail-card, +.info-list, +.metric-tile, +.quick-grid a, +.list-card, +.empty-state { + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius-card); + box-shadow: var(--shadow-panel); +} + +.page-header { + display: flex; + flex-direction: column; + gap: 7px; + padding: 18px; +} + +.page-header h1, +.dashboard-hero h1, +.login-hero h1, +.identity-card h2, +.profile-card h2, +.store-panel h2, +.section-title h2, +.list-card h2, +.list-card h3, +.empty-state h2 { + color: var(--foreground); + letter-spacing: 0; + margin: 0; +} + +.page-header h1 { + font-size: 24px; + font-weight: 750; + line-height: 1.18; +} + +.page-header p, +.dashboard-hero p, +.login-hero p, +.identity-card p, +.profile-card p, +.store-panel p, +.list-card p, +.empty-state p, +.muted-copy { + color: var(--muted); + font-size: 13px; + line-height: 1.6; + margin: 0; +} + +.eyebrow, +.hero-kicker { + color: #a2a9b7; + font-size: 10px; + font-weight: 750; + letter-spacing: 0.1em; + line-height: 1; + margin: 0; + text-transform: uppercase; +} + +.dashboard-hero { + display: flex; + flex-direction: column; + gap: 16px; + overflow: hidden; + padding: 18px; + position: relative; +} + +.dashboard-hero::before { + background: linear-gradient(90deg, #0a0a0a, rgba(0, 0, 0, 0.18)); + content: ""; + height: 3px; + left: 0; + position: absolute; + right: 0; + top: 0; +} + +.dashboard-hero__content { + display: flex; + flex-direction: column; + gap: 8px; + min-width: 0; + padding-top: 2px; +} + +.dashboard-hero h1, +.login-hero h1 { + font-size: 28px; + font-weight: 800; + line-height: 1.12; +} + +.dashboard-hero p:not(.hero-kicker), +.login-hero p:not(.hero-kicker) { + max-width: 28em; +} + +.hero-stat-strip { + background: var(--surface-muted); + border: 1px solid var(--border); + border-radius: var(--radius-card); + display: grid; + gap: 8px; + grid-template-columns: repeat(3, minmax(0, 1fr)); + padding: 8px; +} + +.hero-stat-strip span { + background: var(--surface); + border: 1px solid rgba(0, 0, 0, 0.055); + border-radius: var(--radius-control); + color: var(--muted); + display: flex; + flex-direction: column; + font-size: 12px; + font-weight: 650; + gap: 5px; + min-width: 0; + padding: 11px 10px; +} + +.hero-stat-strip strong { + color: var(--foreground); + font-size: 24px; + font-weight: 800; + line-height: 1; +} + +.identity-card { + align-items: center; + display: grid; + gap: 12px; + grid-template-columns: auto minmax(0, 1fr); + padding: 14px; +} + +.identity-card__media { + align-items: center; + background: var(--surface-muted); + border: 1px solid var(--border); + border-radius: var(--radius-card); + display: flex; + height: 60px; + justify-content: center; + width: 60px; +} + +.identity-card__body { + min-width: 0; +} + +.identity-card h2, +.profile-card h2, +.store-panel h2 { + font-size: 18px; + font-weight: 750; + line-height: 1.25; +} + +.identity-card p, +.profile-card p, +.store-panel p, +.list-card p, +.empty-state p { + margin-top: 5px; +} + +.avatar { + align-items: center; + background: linear-gradient(135deg, rgba(17, 17, 17, 0.94), rgba(97, 97, 97, 0.78)); + border-radius: 50%; + color: #ffffff; + display: inline-flex; + font-size: 19px; + font-weight: 750; + height: 46px; + justify-content: center; + width: 46px; +} + +.avatar.large { + font-size: 25px; + height: 66px; + width: 66px; +} + +.pill-row { + display: flex; + flex-wrap: wrap; + gap: 7px; + margin-top: 10px; +} + +.status-pill { + align-items: center; + background: var(--surface-muted); + border: 1px solid var(--border); + border-radius: 999px; + color: var(--muted-strong); + display: inline-flex; + font-size: 12px; + font-weight: 650; + line-height: 1.2; + min-height: 25px; + padding: 5px 10px; + white-space: nowrap; +} + +.status-pill--default { + background: var(--surface-muted); + color: var(--muted-strong); +} + +.status-pill--success { + background: var(--success-bg); + border-color: rgba(23, 115, 79, 0.22); + color: var(--success); +} + +.status-pill--warning { + background: var(--warning-bg); + border-color: rgba(154, 93, 10, 0.22); + color: var(--warning); +} + +.status-pill--danger { + background: var(--danger-bg); + border-color: rgba(180, 35, 24, 0.22); + color: var(--danger); +} + +.metric-grid, +.quick-grid { + display: grid; + gap: 10px; +} + +.metric-grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.metric-tile { + color: var(--foreground); + display: flex; + flex-direction: column; + gap: 8px; + min-height: 104px; + min-width: 0; + padding: 13px; + transition: border-color 0.16s ease, box-shadow 0.16s ease, transform 0.16s ease; +} + +.metric-tile:hover, +.quick-grid a:hover, +.list-card[href]:hover { + border-color: var(--border-strong); + box-shadow: var(--shadow-floating); +} + +.metric-tile:active, +.quick-grid a:active, +.list-card[href]:active { + transform: translateY(1px); +} + +.metric-tile svg, +.quick-grid svg, +.section-title svg, +.info-list svg, +.store-panel svg, +.empty-state svg { + color: rgba(0, 0, 0, 0.52); + flex: 0 0 auto; +} + +.metric-tile span { + color: var(--foreground); + font-size: 27px; + font-weight: 800; + line-height: 1; +} + +.metric-tile p { + color: var(--muted); + font-size: 12px; + font-weight: 650; + margin: 0; +} + +.section-block { + display: flex; + flex-direction: column; + gap: 12px; + padding: 15px; +} + +.section-title { + align-items: center; + display: flex; + gap: 12px; + justify-content: space-between; + min-width: 0; +} + +.section-title h2 { + font-size: 16px; + font-weight: 750; + line-height: 1.3; +} + +.section-title a, +.inline-link { + align-items: center; + color: var(--muted-strong); + display: inline-flex; + font-size: 13px; + font-weight: 650; + gap: 3px; + min-height: 30px; + white-space: nowrap; +} + +.section-title a:hover, +.inline-link:hover { + color: var(--foreground); +} + +.inline-link { + width: fit-content; +} + +.list-stack { + display: flex; + flex-direction: column; + gap: 10px; +} + +.list-card { + align-items: center; + display: flex; + gap: 12px; + justify-content: space-between; + min-height: 74px; + min-width: 0; + padding: 13px; + transition: border-color 0.16s ease, box-shadow 0.16s ease, transform 0.16s ease; +} + +.list-card > div { + min-width: 0; +} + +.list-card h2, +.list-card h3 { + font-size: 15px; + font-weight: 700; + line-height: 1.35; + overflow-wrap: anywhere; +} + +.quick-grid { + grid-template-columns: repeat(4, minmax(0, 1fr)); +} + +.quick-grid a { + align-items: center; + color: var(--foreground-soft); + display: flex; + flex-direction: column; + font-size: 13px; + font-weight: 650; + gap: 8px; + justify-content: center; + min-height: 76px; + min-width: 0; + padding: 12px 6px; + transition: border-color 0.16s ease, box-shadow 0.16s ease, transform 0.16s ease; +} + +.empty-state { + align-items: center; + border-style: dashed; + box-shadow: none; + color: var(--muted); + display: flex; + flex-direction: column; + gap: 8px; + padding: 24px 14px; + text-align: center; +} + +.empty-state h2 { + font-size: 16px; + font-weight: 700; +} + +.empty-state p { + max-width: 26em; +} + +.segmented { + background: #eeeeec; + border: 1px solid var(--border); + border-radius: var(--radius-card); + display: grid; + gap: 4px; + grid-template-columns: repeat(var(--segment-count, 4), minmax(0, 1fr)); + padding: 4px; +} + +.segmented a, +.segmented button { + align-items: center; + background: transparent; + border: 0; + border-radius: var(--radius-control); + color: var(--muted); + display: inline-flex; + font-size: 13px; + font-weight: 650; + justify-content: center; + min-height: 36px; + padding: 0 6px; +} + +.segmented a.is-active, +.segmented button.is-active { + background: var(--surface); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06); + color: var(--foreground); +} + +.store-panel { + align-items: center; + display: grid; + gap: 12px; + grid-template-columns: auto minmax(0, 1fr) auto; + padding: 15px; +} + +.info-list { + overflow: hidden; +} + +.info-list.compact { + box-shadow: none; +} + +.info-list div { + align-items: center; + border-bottom: 1px solid rgba(0, 0, 0, 0.06); + display: grid; + gap: 10px; + grid-template-columns: auto 78px minmax(0, 1fr); + min-height: 54px; + padding: 0 13px; +} + +.info-list div:last-child { + border-bottom: 0; +} + +.info-list span { + color: var(--muted); + font-size: 14px; +} + +.info-list strong { + color: var(--foreground); + font-size: 14px; + font-weight: 650; + min-width: 0; + overflow-wrap: anywhere; + text-align: right; +} + +.profile-card { + align-items: center; + display: flex; + flex-direction: column; + padding: 24px 16px; + text-align: center; +} + +.detail-card { + display: flex; + flex-direction: column; + gap: 14px; + padding: 15px; +} + +.detail-meta { + align-items: center; + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.detail-meta > span { + align-items: center; + color: var(--muted); + display: inline-flex; + font-size: 13px; + font-weight: 650; + gap: 4px; + margin-right: auto; +} + +.long-copy { + color: var(--foreground); + font-size: 15px; + line-height: 1.76; + white-space: pre-wrap; + word-break: break-word; +} + +.button-row { + display: grid; + gap: 10px; + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.form-stack, +.login-form { + display: flex; + flex-direction: column; + gap: 14px; +} + +.form-stack label, +.login-form label { + display: flex; + flex-direction: column; + gap: 8px; +} + +.form-stack label > span, +.login-form label > span { + color: var(--foreground-soft); + font-size: 13px; + font-weight: 650; +} + +.text-area, +.input-wrap { + background: var(--surface); + border: 1px solid var(--border-strong); + border-radius: var(--radius-input); + color: var(--foreground); + transition: border-color 0.16s ease, box-shadow 0.16s ease, background 0.16s ease; +} + +.text-area { + min-height: 98px; + outline: 0; + padding: 12px; + resize: vertical; + width: 100%; +} + +.input-wrap { + align-items: center; + display: flex; + gap: 8px; + min-height: 42px; + padding: 0 12px; +} + +.input-wrap:focus-within, +.text-area:focus { + border-color: rgba(0, 0, 0, 0.4); + box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.08); +} + +.input-wrap svg { + color: rgba(0, 0, 0, 0.42); + flex: 0 0 auto; +} + +.input-wrap input { + background: transparent; + border: 0; + color: var(--foreground); + flex: 1; + font-size: 14px; + min-width: 0; + outline: 0; +} + +.input-wrap input::placeholder, +.text-area::placeholder { + color: rgba(0, 0, 0, 0.32); +} + +.timeline { + display: flex; + flex-direction: column; + gap: 10px; +} + +.timeline-item { + border-left: 3px solid rgba(0, 0, 0, 0.32); + display: flex; + flex-direction: column; + gap: 4px; + padding: 2px 0 2px 12px; +} + +.timeline-item strong { + font-size: 14px; + line-height: 1.45; +} + +.timeline-item span { + color: var(--muted); + font-size: 12px; +} + +.primary-button, +.secondary-button { + align-items: center; + border: 0; + border-radius: var(--radius-button); + display: inline-flex; + font-size: 13px; + font-weight: 650; + gap: 8px; + justify-content: center; + line-height: 1.2; + min-height: 42px; + padding: 0 16px; + transition: background-color 0.16s ease, border-color 0.16s ease, opacity 0.16s ease; + width: 100%; +} + +.primary-button { + background: var(--accent); + color: var(--accent-ink); +} + +.primary-button:hover { + opacity: 0.86; +} + +.primary-button:disabled, +.secondary-button:disabled { + cursor: not-allowed; + opacity: 0.55; +} + +.secondary-button { + background: var(--surface); + border: 1px solid var(--border-strong); + color: var(--foreground); +} + +.secondary-button:hover { + background: var(--surface-muted); + border-color: rgba(0, 0, 0, 0.24); +} + +.secondary-button.danger { + background: var(--danger-bg); + border-color: rgba(180, 35, 24, 0.2); + color: var(--danger); +} + +.icon-button { + align-items: center; + background: transparent; + border: 0; + border-radius: var(--radius-button); + color: var(--muted); + display: inline-flex; + flex: 0 0 auto; + height: 30px; + justify-content: center; + padding: 0; + width: 30px; +} + +.icon-button:hover, +.icon-button:active { + background: var(--accent-soft); + color: var(--foreground); +} + +.form-error, +.form-success { + border-radius: var(--radius-control); + font-size: 13px; + line-height: 1.5; + margin: 0; + padding: 10px 12px; +} + +.form-error { + background: var(--danger-bg); + border: 1px solid rgba(180, 35, 24, 0.22); + color: var(--danger); +} + +.form-success { + background: var(--success-bg); + border: 1px solid rgba(23, 115, 79, 0.22); + color: var(--success); +} + +.login-page { + display: flex; + flex-direction: column; + gap: 12px; + margin: 0 auto; + max-width: 430px; + min-height: 100vh; + padding: 14px 14px 28px; +} + +.login-hero { + background: var(--surface-muted); + border: 1px solid var(--border); + border-radius: var(--radius-card); + box-shadow: var(--shadow-panel); + display: flex; + flex-direction: column; + gap: 18px; + overflow: hidden; + padding: 18px; + position: relative; +} + +.login-hero::after { + background-image: + linear-gradient(rgba(0, 0, 0, 0.035) 1px, transparent 1px), + linear-gradient(90deg, rgba(0, 0, 0, 0.035) 1px, transparent 1px); + background-size: 32px 32px; + content: ""; + inset: 0; + mask-image: linear-gradient(180deg, black, transparent 74%); + pointer-events: none; + position: absolute; +} + +.login-brand-line { + align-items: center; + color: var(--foreground); + display: inline-flex; + font-size: 14px; + font-weight: 750; + gap: 10px; + position: relative; + z-index: 1; +} + +.brand-mark { + background: var(--foreground); + border-radius: 50%; + display: inline-flex; + height: 24px; + overflow: hidden; + position: relative; + width: 24px; +} + +.brand-mark::before { + background: var(--surface); + border-radius: 50%; + content: ""; + height: 12px; + left: 5px; + position: absolute; + top: 5px; + width: 12px; +} + +.brand-mark::after { + background: var(--foreground); + border-radius: 50%; + content: ""; + height: 10px; + position: absolute; + right: 2px; + top: 12px; + width: 10px; +} + +.login-hero h1, +.login-hero p { + position: relative; + z-index: 1; +} + +.login-preview { + background: rgba(255, 255, 255, 0.7); + border: 1px solid rgba(0, 0, 0, 0.085); + border-radius: var(--radius-card); + display: flex; + flex-direction: column; + gap: 12px; + margin-top: 2px; + padding: 12px; + position: relative; + z-index: 1; +} + +.login-preview__top, +.login-preview__grid { + display: grid; + gap: 8px; +} + +.login-preview__top { + grid-template-columns: 1fr auto; +} + +.login-preview__top span, +.login-preview__grid span { + background: rgba(0, 0, 0, 0.08); + border-radius: 999px; + height: 8px; +} + +.login-preview__top span:first-child { + width: 108px; +} + +.login-preview__top span:last-child { + width: 42px; +} + +.login-preview__body { + background: var(--surface); + border: 1px solid rgba(0, 0, 0, 0.065); + border-radius: var(--radius-control); + color: var(--foreground); + display: flex; + flex-direction: column; + gap: 4px; + padding: 14px; +} + +.login-preview__body strong { + font-size: 28px; + font-weight: 800; + line-height: 1; +} + +.login-preview__body span { + color: var(--muted); + font-size: 13px; + font-weight: 650; +} + +.login-preview__grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.login-preview__grid span { + border-radius: var(--radius-control); + height: 44px; +} + +.login-panel { + padding: 18px; +} + +.login-panel__header { + margin-bottom: 18px; + text-align: center; +} + +.login-panel__header h2 { + color: var(--foreground); + font-size: 22px; + font-weight: 750; + line-height: 1.18; + margin: 0 0 6px; +} + +.login-panel__header p { + color: var(--muted); + font-size: 13px; + line-height: 1.6; + margin: 0; +} + +.bottom-nav { + backdrop-filter: blur(18px); + background: rgba(255, 255, 255, 0.94); + border-top: 1px solid rgba(0, 0, 0, 0.08); + bottom: 0; + box-shadow: 0 -12px 30px rgba(0, 0, 0, 0.08); + display: grid; + grid-template-columns: repeat(5, minmax(0, 1fr)); + left: 0; + min-height: calc(68px + env(safe-area-inset-bottom)); + padding: 8px 8px calc(8px + env(safe-area-inset-bottom)); + position: fixed; + right: 0; + z-index: 20; +} + +.bottom-nav__item { + align-items: center; + border-radius: var(--radius-button); + color: var(--muted); + display: flex; + flex-direction: column; + font-size: 11px; + font-weight: 650; + gap: 4px; + justify-content: center; + min-width: 0; +} + +.bottom-nav__item.is-active { + background: var(--accent); + color: var(--accent-ink); +} + +.skeleton { + animation: pulse 1.2s ease-in-out infinite; + background: linear-gradient(90deg, #ececef, #fafafa, #ececef); + background-size: 200% 100%; + border-radius: var(--radius-card); +} + +.skeleton-title { + height: 72px; +} + +.skeleton-card { + height: 112px; +} + +@keyframes pulse { + 0% { + background-position: 200% 0; + } + + 100% { + background-position: -200% 0; + } +} + +@media (max-width: 390px) { + .app-main, + .login-page { + padding-left: 12px; + padding-right: 12px; + } + + .dashboard-hero h1, + .login-hero h1 { + font-size: 26px; + } + + .metric-tile { + padding: 12px 10px; + } + + .metric-tile span { + font-size: 25px; + } + + .quick-grid a { + font-size: 12px; + } +} + +@media (min-width: 768px) { + .app-main { + padding-top: 24px; + } + + .login-page { + padding-top: 24px; + } + + .page-header h1, + .dashboard-hero h1, + .login-hero h1 { + font-size: 32px; + } + + .bottom-nav { + border: 1px solid rgba(0, 0, 0, 0.08); + border-radius: var(--radius-card); + bottom: 18px; + left: 50%; + max-width: 430px; + min-height: 66px; + padding: 8px; + right: auto; + transform: translateX(-50%); + width: calc(100% - 32px); + } +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx new file mode 100644 index 0000000..f0f59b1 --- /dev/null +++ b/src/app/layout.tsx @@ -0,0 +1,33 @@ +import type { Metadata, Viewport } from "next"; + +import "./globals.css"; + +export const metadata: Metadata = { + title: "员工工作台", + description: "门店员工日常任务、排班、公告和个人中心", + manifest: "/manifest.webmanifest", + icons: { + icon: "/icon.svg", + apple: "/icon.svg" + }, + appleWebApp: { + capable: true, + title: "员工工作台", + statusBarStyle: "default" + } +}; + +export const viewport: Viewport = { + width: "device-width", + initialScale: 1, + maximumScale: 1, + themeColor: "#ffffff" +}; + +export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) { + return ( + + {children} + + ); +} diff --git a/src/app/manifest.ts b/src/app/manifest.ts new file mode 100644 index 0000000..3f882b1 --- /dev/null +++ b/src/app/manifest.ts @@ -0,0 +1,21 @@ +import type { MetadataRoute } from "next"; + +export default function manifest(): MetadataRoute.Manifest { + return { + name: "role-user 员工工作台", + short_name: "员工工作台", + description: "门店员工任务、公告、排班和个人中心", + start_url: "/dashboard", + scope: "/", + display: "standalone", + background_color: "#f4f4f6", + theme_color: "#ffffff", + icons: [ + { + src: "/icon.svg", + sizes: "any", + type: "image/svg+xml" + } + ] + }; +} diff --git a/src/app/page.tsx b/src/app/page.tsx new file mode 100644 index 0000000..9ef1235 --- /dev/null +++ b/src/app/page.tsx @@ -0,0 +1,5 @@ +import { redirect } from "next/navigation"; + +export default function HomePage() { + redirect("/dashboard"); +} diff --git a/src/components/announcement-read-marker.tsx b/src/components/announcement-read-marker.tsx new file mode 100644 index 0000000..fa7bd4a --- /dev/null +++ b/src/components/announcement-read-marker.tsx @@ -0,0 +1,27 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { useEffect, useRef } from "react"; + +type AnnouncementReadMarkerProps = { + id: string; + read: boolean; +}; + +export function AnnouncementReadMarker({ id, read }: AnnouncementReadMarkerProps) { + const router = useRouter(); + const sentRef = useRef(false); + + useEffect(() => { + if (read || sentRef.current) return; + + sentRef.current = true; + void fetch(`/api/mobile/announcements/${encodeURIComponent(id)}/read`, { method: "POST" }).then((response) => { + if (response.ok) { + router.refresh(); + } + }); + }, [id, read, router]); + + return null; +} diff --git a/src/components/bottom-nav.tsx b/src/components/bottom-nav.tsx new file mode 100644 index 0000000..689e657 --- /dev/null +++ b/src/components/bottom-nav.tsx @@ -0,0 +1,33 @@ +"use client"; + +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { Bell, CalendarDays, ClipboardList, Home, UserRound } from "lucide-react"; + +const items = [ + { href: "/dashboard", label: "工作台", icon: Home }, + { href: "/tasks", label: "任务", icon: ClipboardList }, + { href: "/schedule", label: "排班", icon: CalendarDays }, + { href: "/announcements", label: "公告", icon: Bell }, + { href: "/me", label: "我的", icon: UserRound } +] as const; + +export function BottomNav() { + const pathname = usePathname(); + + return ( + + ); +} diff --git a/src/components/empty-state.tsx b/src/components/empty-state.tsx new file mode 100644 index 0000000..b90ffc3 --- /dev/null +++ b/src/components/empty-state.tsx @@ -0,0 +1,16 @@ +import { Inbox } from "lucide-react"; + +type EmptyStateProps = { + title: string; + description: string; +}; + +export function EmptyState({ title, description }: EmptyStateProps) { + return ( +
+ +

{title}

+

{description}

+
+ ); +} diff --git a/src/components/filter-nav.tsx b/src/components/filter-nav.tsx new file mode 100644 index 0000000..8d69b68 --- /dev/null +++ b/src/components/filter-nav.tsx @@ -0,0 +1,35 @@ +import Link from "next/link"; +import type { Route } from "next"; +import type { CSSProperties } from "react"; + +type FilterNavOption = { + href: Route; + label: string; + active: boolean; +}; + +type FilterNavProps = { + label: string; + options: FilterNavOption[]; +}; + +export function FilterNav({ label, options }: FilterNavProps) { + return ( + + ); +} diff --git a/src/components/login-form.tsx b/src/components/login-form.tsx new file mode 100644 index 0000000..77d8bee --- /dev/null +++ b/src/components/login-form.tsx @@ -0,0 +1,83 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { Eye, EyeOff, LockKeyhole, Smartphone } from "lucide-react"; +import { FormEvent, useState } from "react"; + +export function LoginForm() { + const router = useRouter(); + const [error, setError] = useState(""); + const [pending, setPending] = useState(false); + const [passwordVisible, setPasswordVisible] = useState(false); + + async function handleSubmit(event: FormEvent) { + event.preventDefault(); + setError(""); + setPending(true); + + const formData = new FormData(event.currentTarget); + const response = await fetch("/api/auth/login", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + username: String(formData.get("username") || "").trim(), + password: String(formData.get("password") || "") + }) + }); + + if (!response.ok) { + const payload = (await response.json().catch(() => null)) as { message?: string } | null; + setError(payload?.message || "登录失败,请检查手机号和密码"); + setPending(false); + return; + } + + router.replace("/dashboard"); + router.refresh(); + } + + return ( +
+ + + {error ?

{error}

: null} + +
+ ); +} diff --git a/src/components/logout-button.tsx b/src/components/logout-button.tsx new file mode 100644 index 0000000..9849404 --- /dev/null +++ b/src/components/logout-button.tsx @@ -0,0 +1,24 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { LogOut } from "lucide-react"; +import { useState } from "react"; + +export function LogoutButton() { + const router = useRouter(); + const [pending, setPending] = useState(false); + + async function logout() { + setPending(true); + await fetch("/api/auth/logout", { method: "POST" }); + router.replace("/login"); + router.refresh(); + } + + return ( + + ); +} diff --git a/src/components/page-header.tsx b/src/components/page-header.tsx new file mode 100644 index 0000000..22ab6f9 --- /dev/null +++ b/src/components/page-header.tsx @@ -0,0 +1,15 @@ +type PageHeaderProps = { + eyebrow?: string; + title: string; + description?: string; +}; + +export function PageHeader({ eyebrow, title, description }: PageHeaderProps) { + return ( +
+ {eyebrow ?

{eyebrow}

: null} +

{title}

+ {description ?

{description}

: null} +
+ ); +} diff --git a/src/components/password-change-form.tsx b/src/components/password-change-form.tsx new file mode 100644 index 0000000..e3b72b5 --- /dev/null +++ b/src/components/password-change-form.tsx @@ -0,0 +1,84 @@ +"use client"; + +import { KeyRound } from "lucide-react"; +import { FormEvent, useState } from "react"; + +import type { ApiEnvelope } from "@/lib/types"; + +async function readError(response: Response) { + const payload = (await response.json().catch(() => null)) as Partial> | null; + return payload?.message || "修改密码失败"; +} + +export function PasswordChangeForm() { + const [pending, setPending] = useState(false); + const [error, setError] = useState(""); + const [message, setMessage] = useState(""); + + async function handleSubmit(event: FormEvent) { + event.preventDefault(); + setError(""); + setMessage(""); + setPending(true); + + const form = event.currentTarget; + const formData = new FormData(form); + const oldPassword = String(formData.get("oldPassword") || ""); + const newPassword = String(formData.get("newPassword") || ""); + const confirmPassword = String(formData.get("confirmPassword") || ""); + + if (newPassword !== confirmPassword) { + setError("两次输入的新密码不一致"); + setPending(false); + return; + } + + const response = await fetch("/api/auth/me/password", { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ oldPassword, newPassword }) + }); + + if (!response.ok) { + setError(await readError(response)); + setPending(false); + return; + } + + form.reset(); + setMessage("密码已修改"); + setPending(false); + } + + return ( +
+ + + + {error ?

{error}

: null} + {message ?

{message}

: null} + +
+ ); +} diff --git a/src/components/status-pill.tsx b/src/components/status-pill.tsx new file mode 100644 index 0000000..f80ce01 --- /dev/null +++ b/src/components/status-pill.tsx @@ -0,0 +1,8 @@ +type StatusPillProps = { + children: React.ReactNode; + tone?: "default" | "success" | "warning" | "danger"; +}; + +export function StatusPill({ children, tone = "default" }: StatusPillProps) { + return {children}; +} diff --git a/src/components/task-action-panel.tsx b/src/components/task-action-panel.tsx new file mode 100644 index 0000000..4e8c143 --- /dev/null +++ b/src/components/task-action-panel.tsx @@ -0,0 +1,145 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { CheckCircle2, MessageSquarePlus, PlayCircle } from "lucide-react"; +import { FormEvent, useState } from "react"; + +import type { ApiEnvelope, TaskDetail, TaskStatus } from "@/lib/types"; + +type TaskActionPanelProps = { + taskId: string; + initialStatus: TaskStatus; +}; + +type TaskAction = "start" | "complete" | "comment"; + +function normalizeStatus(value: unknown): TaskStatus | null { + switch (value) { + case "PENDING": + case "pending": + return "pending"; + case "IN_PROGRESS": + case "in_progress": + return "in_progress"; + case "COMPLETED": + case "completed": + return "completed"; + case "CANCELLED": + case "cancelled": + return "cancelled"; + default: + return null; + } +} + +async function readMessage(response: Response) { + const payload = (await response.json().catch(() => null)) as Partial> | null; + return payload?.message || "操作失败"; +} + +export function TaskActionPanel({ taskId, initialStatus }: TaskActionPanelProps) { + const router = useRouter(); + const [status, setStatus] = useState(initialStatus); + const [pendingAction, setPendingAction] = useState(null); + const [message, setMessage] = useState(""); + const [error, setError] = useState(""); + + async function runAction(action: Exclude) { + setError(""); + setMessage(""); + setPendingAction(action); + + const response = await fetch(`/api/mobile/tasks/${encodeURIComponent(taskId)}/${action}`, { method: "POST" }); + + if (!response.ok) { + setError(await readMessage(response)); + setPendingAction(null); + return; + } + + const payload = (await response.json().catch(() => null)) as ApiEnvelope | null; + const nextStatus = normalizeStatus(payload?.data?.status); + if (nextStatus) setStatus(nextStatus); + + setMessage(action === "start" ? "任务已开始处理" : "任务已完成"); + setPendingAction(null); + router.refresh(); + } + + async function submitComment(event: FormEvent) { + event.preventDefault(); + setError(""); + setMessage(""); + setPendingAction("comment"); + + const form = event.currentTarget; + const formData = new FormData(form); + const comment = String(formData.get("comment") || "").trim(); + + if (!comment) { + setError("请填写备注内容"); + setPendingAction(null); + return; + } + + const response = await fetch(`/api/mobile/tasks/${encodeURIComponent(taskId)}/comment`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ comment }) + }); + + if (!response.ok) { + setError(await readMessage(response)); + setPendingAction(null); + return; + } + + form.reset(); + setMessage("备注已追加"); + setPendingAction(null); + router.refresh(); + } + + const canStart = status === "pending"; + const canComplete = status === "pending" || status === "in_progress"; + + return ( +
+
+

处理任务

+
+
+ + +
+
+