diff --git a/README.md b/README.md
index 0d90a26..3dc49ba 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# PwrProgram
-A comprehensive fitness program management API built with TypeScript, Node.js, and PostgreSQL. Manage workout programs, cycles, blocks, sessions, exercises, and sets with a hierarchical structure designed for coaches and athletes.
+A comprehensive fitness program management application built with TypeScript, React, Node.js, and PostgreSQL. Manage workout programs, cycles, blocks, sessions, exercises, and sets with a hierarchical structure designed for coaches and athletes.
## 🚀 Features
@@ -11,6 +11,14 @@ A comprehensive fitness program management API built with TypeScript, Node.js, a
- **Pagination**: Efficient data retrieval with configurable page sizes
- **HATEOAS**: Discoverable REST API with hypermedia links
+### Frontend (React + Vite)
+- **Modern Stack**: React 19, TypeScript, Vite
+- **Routing**: React Router v7 with protected routes
+- **Authentication**: Context-based auth with session management
+- **Component Library**: Reusable UI components (Button, Input, Card)
+- **CSS Modules**: Scoped styling for maintainability
+- **API Integration**: Type-safe API client with error handling
+
### Security
- ✅ **Password Hashing**: Bcrypt with configurable rounds
- ✅ **Session Management**: Database-backed sessions (TypeORM store, scalable to Redis)
@@ -142,26 +150,32 @@ All configuration is done through environment variables. Copy `.env.example` to
### Development
```bash
-# Start dev server with hot reload
-pnpm --filter @pwrprogram/api dev
+# Start API server with hot reload
+pnpm dev:api
-# Run tests
-pnpm --filter @pwrprogram/api test
+# Start frontend dev server
+pnpm dev:web
+
+# Run API tests
+pnpm test:api
# Run tests with coverage
-pnpm --filter @pwrprogram/api test:coverage
+pnpm test:api:coverage
-# Lint code
-pnpm --filter @pwrprogram/api lint
+# Lint frontend code
+pnpm lint:web
```
### Production
```bash
-# Build the application
+# Build the API
pnpm --filter @pwrprogram/api build
-# Start production server
-pnpm --filter @pwrprogram/api start
+# Build the frontend
+pnpm build:web
+
+# Start production API server
+pnpm start:api
```
### Docker
@@ -430,18 +444,34 @@ docker run -d \
```
PwrProgram/
├── apps/
-│ └── pwrprogram/ # Main API application
+│ ├── pwrprogram/ # Main API application
+│ │ ├── src/
+│ │ │ ├── entity/ # TypeORM entities
+│ │ │ ├── routes/ # Express route handlers
+│ │ │ ├── middleware/ # Custom middleware
+│ │ │ ├── mappers/ # Entity to DTO mappers
+│ │ │ ├── utils/ # Utility functions
+│ │ │ ├── openapi/ # OpenAPI spec
+│ │ │ ├── testing/ # Test files
+│ │ │ ├── data-source.ts # TypeORM configuration
+│ │ │ └── index.ts # Application entry point
+│ │ ├── .env # Environment variables (gitignored)
+│ │ └── package.json
+│ └── web/ # React frontend application
│ ├── src/
-│ │ ├── entity/ # TypeORM entities
-│ │ ├── routes/ # Express route handlers
-│ │ ├── middleware/ # Custom middleware
-│ │ ├── mappers/ # Entity to DTO mappers
-│ │ ├── utils/ # Utility functions
-│ │ ├── openapi/ # OpenAPI spec
-│ │ ├── testing/ # Test files
-│ │ ├── data-source.ts # TypeORM configuration
-│ │ └── index.ts # Application entry point
-│ ├── .env # Environment variables (gitignored)
+│ │ ├── components/ # React components
+│ │ │ ├── auth/ # Auth-related components
+│ │ │ ├── layout/ # Layout components
+│ │ │ ├── programs/ # Program management
+│ │ │ └── ui/ # Reusable UI components
+│ │ ├── context/ # React contexts (Auth)
+│ │ ├── hooks/ # Custom React hooks
+│ │ ├── lib/ # Utilities and API client
+│ │ ├── pages/ # Page components
+│ │ ├── types/ # TypeScript types
+│ │ ├── App.tsx # App with routing
+│ │ └── main.tsx # Entry point
+│ ├── vite.config.ts # Vite configuration
│ └── package.json
├── packages/
│ └── shared/ # Shared DTOs and types
@@ -546,7 +576,5 @@ PwrProgram/
- [Express.js](https://expressjs.com/)
- [PostgreSQL](https://www.postgresql.org/)
- [TypeScript](https://www.typescriptlang.org/)
-
----
-
-**Note**: This is a backend API. Frontend coming soon!
+- [React](https://react.dev/)
+- [Vite](https://vitejs.dev/)
diff --git a/apps/web/.gitignore b/apps/web/.gitignore
new file mode 100644
index 0000000..a547bf3
--- /dev/null
+++ b/apps/web/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/apps/web/README.md b/apps/web/README.md
new file mode 100644
index 0000000..d2e7761
--- /dev/null
+++ b/apps/web/README.md
@@ -0,0 +1,73 @@
+# React + TypeScript + Vite
+
+This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
+
+Currently, two official plugins are available:
+
+- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
+- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
+
+## React Compiler
+
+The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
+
+## Expanding the ESLint configuration
+
+If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
+
+```js
+export default defineConfig([
+ globalIgnores(['dist']),
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [
+ // Other configs...
+
+ // Remove tseslint.configs.recommended and replace with this
+ tseslint.configs.recommendedTypeChecked,
+ // Alternatively, use this for stricter rules
+ tseslint.configs.strictTypeChecked,
+ // Optionally, add this for stylistic rules
+ tseslint.configs.stylisticTypeChecked,
+
+ // Other configs...
+ ],
+ languageOptions: {
+ parserOptions: {
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
+ tsconfigRootDir: import.meta.dirname,
+ },
+ // other options...
+ },
+ },
+])
+```
+
+You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
+
+```js
+// eslint.config.js
+import reactX from 'eslint-plugin-react-x'
+import reactDom from 'eslint-plugin-react-dom'
+
+export default defineConfig([
+ globalIgnores(['dist']),
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [
+ // Other configs...
+ // Enable lint rules for React
+ reactX.configs['recommended-typescript'],
+ // Enable lint rules for React DOM
+ reactDom.configs.recommended,
+ ],
+ languageOptions: {
+ parserOptions: {
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
+ tsconfigRootDir: import.meta.dirname,
+ },
+ // other options...
+ },
+ },
+])
+```
diff --git a/apps/web/eslint.config.js b/apps/web/eslint.config.js
new file mode 100644
index 0000000..0785165
--- /dev/null
+++ b/apps/web/eslint.config.js
@@ -0,0 +1,29 @@
+import js from '@eslint/js'
+import globals from 'globals'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+import tseslint from 'typescript-eslint'
+import { defineConfig, globalIgnores } from 'eslint/config'
+
+export default defineConfig([
+ globalIgnores(['dist']),
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [
+ js.configs.recommended,
+ tseslint.configs.recommended,
+ reactHooks.configs.flat.recommended,
+ reactRefresh.configs.vite,
+ ],
+ languageOptions: {
+ ecmaVersion: 2020,
+ globals: globals.browser,
+ },
+ rules: {
+ 'react-refresh/only-export-components': [
+ 'warn',
+ { allowExportNames: ['AuthContext', 'AuthContextType'] },
+ ],
+ },
+ },
+])
diff --git a/apps/web/index.html b/apps/web/index.html
new file mode 100644
index 0000000..5482e67
--- /dev/null
+++ b/apps/web/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ PwrProgram - Fitness Program Management
+
+
+
+
+
+
diff --git a/apps/web/package.json b/apps/web/package.json
new file mode 100644
index 0000000..267e907
--- /dev/null
+++ b/apps/web/package.json
@@ -0,0 +1,33 @@
+{
+ "name": "@pwrprogram/web",
+ "private": true,
+ "version": "1.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc -b && vite build",
+ "lint": "eslint .",
+ "preview": "vite preview",
+ "typecheck": "tsc --noEmit"
+ },
+ "dependencies": {
+ "@pwrprogram/shared": "workspace:*",
+ "react": "^19.2.0",
+ "react-dom": "^19.2.0",
+ "react-router-dom": "^7.6.1"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.39.1",
+ "@types/node": "^24.10.1",
+ "@types/react": "^19.2.5",
+ "@types/react-dom": "^19.2.3",
+ "@vitejs/plugin-react": "^5.1.1",
+ "eslint": "^9.39.1",
+ "eslint-plugin-react-hooks": "^7.0.1",
+ "eslint-plugin-react-refresh": "^0.4.24",
+ "globals": "^16.5.0",
+ "typescript": "~5.9.3",
+ "typescript-eslint": "^8.46.4",
+ "vite": "^7.2.4"
+ }
+}
diff --git a/apps/web/public/vite.svg b/apps/web/public/vite.svg
new file mode 100644
index 0000000..e7b8dfb
--- /dev/null
+++ b/apps/web/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/web/src/App.css b/apps/web/src/App.css
new file mode 100644
index 0000000..928faf3
--- /dev/null
+++ b/apps/web/src/App.css
@@ -0,0 +1,20 @@
+/* Global App Styles */
+* {
+ box-sizing: border-box;
+}
+
+#root {
+ min-height: 100vh;
+}
+
+a {
+ text-decoration: none;
+ color: inherit;
+}
+
+/* Links styled by components */
+a:focus-visible {
+ outline: 2px solid #3b82f6;
+ outline-offset: 2px;
+}
+
diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx
new file mode 100644
index 0000000..cd36b34
--- /dev/null
+++ b/apps/web/src/App.tsx
@@ -0,0 +1,58 @@
+import { BrowserRouter, Routes, Route } from 'react-router-dom';
+import { AuthProvider } from '@/context';
+import { MainLayout } from '@/components/layout';
+import { ProtectedRoute } from '@/components/auth';
+import {
+ HomePage,
+ LoginPage,
+ RegisterPage,
+ DashboardPage,
+ ProgramsPage,
+ ProgramDetailPage,
+} from '@/pages';
+import './App.css';
+
+function App() {
+ return (
+
+
+
+ }>
+ {/* Public routes */}
+ } />
+ } />
+ } />
+
+ {/* Protected routes */}
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+
+ );
+}
+
+export default App;
diff --git a/apps/web/src/assets/react.svg b/apps/web/src/assets/react.svg
new file mode 100644
index 0000000..6c87de9
--- /dev/null
+++ b/apps/web/src/assets/react.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/web/src/components/auth/ProtectedRoute.tsx b/apps/web/src/components/auth/ProtectedRoute.tsx
new file mode 100644
index 0000000..5385128
--- /dev/null
+++ b/apps/web/src/components/auth/ProtectedRoute.tsx
@@ -0,0 +1,31 @@
+import { Navigate, useLocation } from 'react-router-dom';
+import { useAuth } from '@/context';
+
+interface ProtectedRouteProps {
+ children: React.ReactNode;
+}
+
+export function ProtectedRoute({ children }: ProtectedRouteProps) {
+ const { isAuthenticated, isLoading } = useAuth();
+ const location = useLocation();
+
+ if (isLoading) {
+ return (
+
+ Loading...
+
+ );
+ }
+
+ if (!isAuthenticated) {
+ return ;
+ }
+
+ return <>{children}>;
+}
diff --git a/apps/web/src/components/auth/index.ts b/apps/web/src/components/auth/index.ts
new file mode 100644
index 0000000..f9cff2a
--- /dev/null
+++ b/apps/web/src/components/auth/index.ts
@@ -0,0 +1 @@
+export { ProtectedRoute } from './ProtectedRoute';
diff --git a/apps/web/src/components/layout/MainLayout.module.css b/apps/web/src/components/layout/MainLayout.module.css
new file mode 100644
index 0000000..71078f9
--- /dev/null
+++ b/apps/web/src/components/layout/MainLayout.module.css
@@ -0,0 +1,92 @@
+.layout {
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+}
+
+.header {
+ background: white;
+ border-bottom: 1px solid #e5e7eb;
+ position: sticky;
+ top: 0;
+ z-index: 100;
+}
+
+.headerContent {
+ max-width: 1280px;
+ margin: 0 auto;
+ padding: 1rem 2rem;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 2rem;
+}
+
+.logo {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ text-decoration: none;
+ color: #1e293b;
+}
+
+.logoIcon {
+ font-size: 1.5rem;
+}
+
+.logoText {
+ font-size: 1.25rem;
+ font-weight: 700;
+}
+
+.nav {
+ display: flex;
+ gap: 1.5rem;
+ flex: 1;
+}
+
+.navLink {
+ text-decoration: none;
+ color: #64748b;
+ font-weight: 500;
+ transition: color 0.2s;
+}
+
+.navLink:hover {
+ color: #3b82f6;
+}
+
+.actions {
+ display: flex;
+ align-items: center;
+}
+
+.userMenu {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+}
+
+.userName {
+ color: #374151;
+ font-weight: 500;
+}
+
+.authButtons {
+ display: flex;
+ gap: 0.5rem;
+}
+
+.main {
+ flex: 1;
+ background: #f8fafc;
+}
+
+.footer {
+ background: white;
+ border-top: 1px solid #e5e7eb;
+ padding: 1.5rem 2rem;
+ text-align: center;
+ color: #64748b;
+ font-size: 0.875rem;
+}
diff --git a/apps/web/src/components/layout/MainLayout.tsx b/apps/web/src/components/layout/MainLayout.tsx
new file mode 100644
index 0000000..e016559
--- /dev/null
+++ b/apps/web/src/components/layout/MainLayout.tsx
@@ -0,0 +1,70 @@
+import { Outlet, Link, useNavigate } from 'react-router-dom';
+import { useAuth } from '@/context';
+import { Button } from '@/components/ui';
+import styles from './MainLayout.module.css';
+
+export function MainLayout() {
+ const { user, logout, isAuthenticated } = useAuth();
+ const navigate = useNavigate();
+
+ const handleLogout = async () => {
+ await logout();
+ navigate('/login');
+ };
+
+ return (
+
+
+
+
+
💪
+
PwrProgram
+
+
+
+ {isAuthenticated ? (
+ <>
+
+ Dashboard
+
+
+ Programs
+
+ >
+ ) : null}
+
+
+
+ {isAuthenticated ? (
+
+
+ {user?.firstName} {user?.lastName}
+
+
+ Logout
+
+
+ ) : (
+
+
+ Login
+
+
+ Sign Up
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/web/src/components/layout/index.ts b/apps/web/src/components/layout/index.ts
new file mode 100644
index 0000000..bfa7d49
--- /dev/null
+++ b/apps/web/src/components/layout/index.ts
@@ -0,0 +1 @@
+export { MainLayout } from './MainLayout';
diff --git a/apps/web/src/components/ui/Button.module.css b/apps/web/src/components/ui/Button.module.css
new file mode 100644
index 0000000..09c6809
--- /dev/null
+++ b/apps/web/src/components/ui/Button.module.css
@@ -0,0 +1,86 @@
+.button {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ font-weight: 500;
+ border-radius: 8px;
+ border: none;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ font-family: inherit;
+}
+
+.button:disabled {
+ opacity: 0.6;
+ cursor: not-allowed;
+}
+
+/* Variants */
+.primary {
+ background: #3b82f6;
+ color: white;
+}
+
+.primary:hover:not(:disabled) {
+ background: #2563eb;
+}
+
+.secondary {
+ background: #f1f5f9;
+ color: #1e293b;
+}
+
+.secondary:hover:not(:disabled) {
+ background: #e2e8f0;
+}
+
+.danger {
+ background: #ef4444;
+ color: white;
+}
+
+.danger:hover:not(:disabled) {
+ background: #dc2626;
+}
+
+.ghost {
+ background: transparent;
+ color: #64748b;
+}
+
+.ghost:hover:not(:disabled) {
+ background: #f1f5f9;
+ color: #1e293b;
+}
+
+/* Sizes */
+.sm {
+ padding: 0.5rem 1rem;
+ font-size: 0.875rem;
+}
+
+.md {
+ padding: 0.75rem 1.5rem;
+ font-size: 1rem;
+}
+
+.lg {
+ padding: 1rem 2rem;
+ font-size: 1.125rem;
+}
+
+/* Loading spinner */
+.spinner {
+ width: 1em;
+ height: 1em;
+ border: 2px solid transparent;
+ border-top-color: currentColor;
+ border-radius: 50%;
+ animation: spin 0.7s linear infinite;
+}
+
+@keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
diff --git a/apps/web/src/components/ui/Button.tsx b/apps/web/src/components/ui/Button.tsx
new file mode 100644
index 0000000..408e1e9
--- /dev/null
+++ b/apps/web/src/components/ui/Button.tsx
@@ -0,0 +1,29 @@
+import type { ButtonHTMLAttributes, ReactNode } from 'react';
+import styles from './Button.module.css';
+
+interface ButtonProps extends ButtonHTMLAttributes {
+ variant?: 'primary' | 'secondary' | 'danger' | 'ghost';
+ size?: 'sm' | 'md' | 'lg';
+ isLoading?: boolean;
+ children: ReactNode;
+}
+
+export function Button({
+ variant = 'primary',
+ size = 'md',
+ isLoading = false,
+ children,
+ disabled,
+ className = '',
+ ...props
+}: ButtonProps) {
+ return (
+
+ {isLoading ? : children}
+
+ );
+}
diff --git a/apps/web/src/components/ui/Card.module.css b/apps/web/src/components/ui/Card.module.css
new file mode 100644
index 0000000..eb5a6b9
--- /dev/null
+++ b/apps/web/src/components/ui/Card.module.css
@@ -0,0 +1,32 @@
+.card {
+ background: white;
+ border-radius: 12px;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+ border: 1px solid #e5e7eb;
+ overflow: hidden;
+}
+
+.clickable {
+ cursor: pointer;
+ transition: box-shadow 0.2s, transform 0.2s;
+}
+
+.clickable:hover {
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+ transform: translateY(-2px);
+}
+
+.header {
+ padding: 1.25rem 1.5rem;
+ border-bottom: 1px solid #e5e7eb;
+}
+
+.content {
+ padding: 1.5rem;
+}
+
+.footer {
+ padding: 1rem 1.5rem;
+ border-top: 1px solid #e5e7eb;
+ background: #f9fafb;
+}
diff --git a/apps/web/src/components/ui/Card.tsx b/apps/web/src/components/ui/Card.tsx
new file mode 100644
index 0000000..28340a1
--- /dev/null
+++ b/apps/web/src/components/ui/Card.tsx
@@ -0,0 +1,33 @@
+import type { ReactNode } from 'react';
+import styles from './Card.module.css';
+
+interface CardProps {
+ children: ReactNode;
+ className?: string;
+ onClick?: () => void;
+}
+
+export function Card({ children, className = '', onClick }: CardProps) {
+ return (
+
+ {children}
+
+ );
+}
+
+export function CardHeader({ children, className = '' }: { children: ReactNode; className?: string }) {
+ return {children}
;
+}
+
+export function CardContent({ children, className = '' }: { children: ReactNode; className?: string }) {
+ return {children}
;
+}
+
+export function CardFooter({ children, className = '' }: { children: ReactNode; className?: string }) {
+ return {children}
;
+}
diff --git a/apps/web/src/components/ui/Input.module.css b/apps/web/src/components/ui/Input.module.css
new file mode 100644
index 0000000..efbc26c
--- /dev/null
+++ b/apps/web/src/components/ui/Input.module.css
@@ -0,0 +1,46 @@
+.container {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ width: 100%;
+}
+
+.label {
+ font-size: 0.875rem;
+ font-weight: 500;
+ color: #374151;
+}
+
+.input {
+ padding: 0.75rem 1rem;
+ border: 1px solid #d1d5db;
+ border-radius: 8px;
+ font-size: 1rem;
+ font-family: inherit;
+ transition: border-color 0.2s, box-shadow 0.2s;
+ outline: none;
+ width: 100%;
+ box-sizing: border-box;
+}
+
+.input:focus {
+ border-color: #3b82f6;
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
+}
+
+.input.error {
+ border-color: #ef4444;
+}
+
+.input.error:focus {
+ box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
+}
+
+.errorText {
+ font-size: 0.875rem;
+ color: #ef4444;
+}
+
+.input::placeholder {
+ color: #9ca3af;
+}
diff --git a/apps/web/src/components/ui/Input.tsx b/apps/web/src/components/ui/Input.tsx
new file mode 100644
index 0000000..d0ef246
--- /dev/null
+++ b/apps/web/src/components/ui/Input.tsx
@@ -0,0 +1,33 @@
+import type { InputHTMLAttributes } from 'react';
+import { forwardRef } from 'react';
+import styles from './Input.module.css';
+
+interface InputProps extends InputHTMLAttributes {
+ label?: string;
+ error?: string;
+}
+
+export const Input = forwardRef(
+ ({ label, error, className = '', id, ...props }, ref) => {
+ const inputId = id || props.name;
+
+ return (
+
+ {label && (
+
+ {label}
+
+ )}
+
+ {error && {error} }
+
+ );
+ }
+);
+
+Input.displayName = 'Input';
diff --git a/apps/web/src/components/ui/index.ts b/apps/web/src/components/ui/index.ts
new file mode 100644
index 0000000..8800505
--- /dev/null
+++ b/apps/web/src/components/ui/index.ts
@@ -0,0 +1,3 @@
+export { Button } from './Button';
+export { Input } from './Input';
+export { Card, CardHeader, CardContent, CardFooter } from './Card';
diff --git a/apps/web/src/context/AuthContext.tsx b/apps/web/src/context/AuthContext.tsx
new file mode 100644
index 0000000..340f1ab
--- /dev/null
+++ b/apps/web/src/context/AuthContext.tsx
@@ -0,0 +1,124 @@
+import {
+ createContext,
+ useState,
+ useEffect,
+ type ReactNode,
+} from 'react';
+import { api } from '@/lib/api';
+import type { AuthState } from '@/types';
+
+export interface AuthContextType extends AuthState {
+ login: (email: string, password: string) => Promise;
+ register: (data: {
+ email: string;
+ password: string;
+ firstName: string;
+ lastName?: string;
+ }) => Promise;
+ logout: () => Promise;
+ refreshUser: () => Promise;
+}
+
+export const AuthContext = createContext(undefined);
+
+export function AuthProvider({ children }: { children: ReactNode }) {
+ const [state, setState] = useState({
+ user: null,
+ isAuthenticated: false,
+ isLoading: true,
+ });
+
+ const refreshUser = async () => {
+ try {
+ const user = await api.getCurrentUser();
+ setState({
+ user,
+ isAuthenticated: true,
+ isLoading: false,
+ });
+ } catch {
+ setState({
+ user: null,
+ isAuthenticated: false,
+ isLoading: false,
+ });
+ }
+ };
+
+ useEffect(() => {
+ let cancelled = false;
+
+ async function checkAuth() {
+ try {
+ const user = await api.getCurrentUser();
+ if (!cancelled) {
+ setState({
+ user,
+ isAuthenticated: true,
+ isLoading: false,
+ });
+ }
+ } catch {
+ if (!cancelled) {
+ setState({
+ user: null,
+ isAuthenticated: false,
+ isLoading: false,
+ });
+ }
+ }
+ }
+
+ checkAuth();
+
+ return () => {
+ cancelled = true;
+ };
+ }, []);
+
+ const login = async (email: string, password: string) => {
+ const user = await api.login(email, password);
+ setState({
+ user,
+ isAuthenticated: true,
+ isLoading: false,
+ });
+ };
+
+ const register = async (data: {
+ email: string;
+ password: string;
+ firstName: string;
+ lastName?: string;
+ }) => {
+ const user = await api.register(data);
+ setState({
+ user,
+ isAuthenticated: true,
+ isLoading: false,
+ });
+ };
+
+ const logout = async () => {
+ await api.logout();
+ setState({
+ user: null,
+ isAuthenticated: false,
+ isLoading: false,
+ });
+ };
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/apps/web/src/context/index.ts b/apps/web/src/context/index.ts
new file mode 100644
index 0000000..41f0cf0
--- /dev/null
+++ b/apps/web/src/context/index.ts
@@ -0,0 +1,2 @@
+export { AuthProvider, AuthContext, type AuthContextType } from './AuthContext';
+export { useAuth } from './useAuth';
diff --git a/apps/web/src/context/useAuth.ts b/apps/web/src/context/useAuth.ts
new file mode 100644
index 0000000..43a99ec
--- /dev/null
+++ b/apps/web/src/context/useAuth.ts
@@ -0,0 +1,10 @@
+import { useContext } from 'react';
+import { AuthContext, type AuthContextType } from './AuthContext';
+
+export function useAuth(): AuthContextType {
+ const context = useContext(AuthContext);
+ if (context === undefined) {
+ throw new Error('useAuth must be used within an AuthProvider');
+ }
+ return context;
+}
diff --git a/apps/web/src/index.css b/apps/web/src/index.css
new file mode 100644
index 0000000..2fb8383
--- /dev/null
+++ b/apps/web/src/index.css
@@ -0,0 +1,40 @@
+:root {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
+ line-height: 1.5;
+ font-weight: 400;
+
+ color: #1e293b;
+ background-color: #f8fafc;
+
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+body {
+ margin: 0;
+ min-width: 320px;
+ min-height: 100vh;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ margin: 0;
+ line-height: 1.2;
+}
+
+p {
+ margin: 0;
+}
+
+input, button, textarea, select {
+ font: inherit;
+}
+
+/* Remove default button styling */
+button {
+ background: none;
+ border: none;
+ padding: 0;
+ cursor: pointer;
+}
diff --git a/apps/web/src/lib/api.ts b/apps/web/src/lib/api.ts
new file mode 100644
index 0000000..bcba218
--- /dev/null
+++ b/apps/web/src/lib/api.ts
@@ -0,0 +1,120 @@
+import type { User, Program, PaginatedResponse, ApiError } from '@/types';
+
+const API_BASE = '/api';
+
+class ApiClient {
+ private async request(
+ endpoint: string,
+ options: RequestInit = {}
+ ): Promise {
+ const url = `${API_BASE}${endpoint}`;
+
+ const response = await fetch(url, {
+ ...options,
+ headers: {
+ 'Content-Type': 'application/json',
+ ...options.headers,
+ },
+ credentials: 'include',
+ });
+
+ if (!response.ok) {
+ const error: ApiError = await response.json().catch(() => ({
+ message: 'An unexpected error occurred',
+ statusCode: response.status,
+ }));
+ throw error;
+ }
+
+ // Handle 204 No Content
+ if (response.status === 204) {
+ return undefined as T;
+ }
+
+ return response.json();
+ }
+
+ // Auth endpoints
+ async login(email: string, password: string): Promise {
+ return this.request('/auth/login', {
+ method: 'POST',
+ body: JSON.stringify({ email, password }),
+ });
+ }
+
+ async register(data: {
+ email: string;
+ password: string;
+ firstName: string;
+ lastName?: string;
+ }): Promise {
+ return this.request('/auth/register', {
+ method: 'POST',
+ body: JSON.stringify(data),
+ });
+ }
+
+ async logout(): Promise {
+ return this.request('/auth/logout', {
+ method: 'POST',
+ });
+ }
+
+ async getCurrentUser(): Promise {
+ return this.request('/auth/me');
+ }
+
+ // User endpoints
+ async getUser(id: string): Promise {
+ return this.request(`/users/${id}`);
+ }
+
+ async updateUser(
+ id: string,
+ data: Partial>
+ ): Promise {
+ return this.request(`/users/${id}`, {
+ method: 'PATCH',
+ body: JSON.stringify(data),
+ });
+ }
+
+ // Program endpoints
+ async getPrograms(page = 1, limit = 10): Promise> {
+ return this.request>(
+ `/programs?page=${page}&limit=${limit}`
+ );
+ }
+
+ async getProgram(id: string): Promise {
+ return this.request(`/programs/${id}`);
+ }
+
+ async createProgram(data: {
+ name: string;
+ description?: string;
+ }): Promise {
+ return this.request('/programs', {
+ method: 'POST',
+ body: JSON.stringify(data),
+ });
+ }
+
+ async updateProgram(
+ id: string,
+ data: { name?: string; description?: string }
+ ): Promise {
+ return this.request(`/programs/${id}`, {
+ method: 'PATCH',
+ body: JSON.stringify(data),
+ });
+ }
+
+ async deleteProgram(id: string): Promise {
+ return this.request(`/programs/${id}`, {
+ method: 'DELETE',
+ });
+ }
+}
+
+export const api = new ApiClient();
diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx
new file mode 100644
index 0000000..bef5202
--- /dev/null
+++ b/apps/web/src/main.tsx
@@ -0,0 +1,10 @@
+import { StrictMode } from 'react'
+import { createRoot } from 'react-dom/client'
+import './index.css'
+import App from './App.tsx'
+
+createRoot(document.getElementById('root')!).render(
+
+
+ ,
+)
diff --git a/apps/web/src/pages/AuthPages.module.css b/apps/web/src/pages/AuthPages.module.css
new file mode 100644
index 0000000..0f98156
--- /dev/null
+++ b/apps/web/src/pages/AuthPages.module.css
@@ -0,0 +1,66 @@
+.container {
+ min-height: calc(100vh - 180px);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 2rem;
+}
+
+.card {
+ width: 100%;
+ max-width: 420px;
+}
+
+.title {
+ font-size: 1.5rem;
+ font-weight: 700;
+ color: #1e293b;
+ margin: 0;
+}
+
+.subtitle {
+ color: #64748b;
+ margin: 0.5rem 0 0;
+}
+
+.form {
+ display: flex;
+ flex-direction: column;
+ gap: 1.25rem;
+}
+
+.row {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 1rem;
+}
+
+.error {
+ padding: 0.75rem 1rem;
+ background: #fef2f2;
+ border: 1px solid #fee2e2;
+ border-radius: 8px;
+ color: #dc2626;
+ font-size: 0.875rem;
+}
+
+.submitBtn {
+ width: 100%;
+ margin-top: 0.5rem;
+}
+
+.footer {
+ text-align: center;
+ margin-top: 1.5rem;
+ color: #64748b;
+}
+
+.link {
+ color: #3b82f6;
+ text-decoration: none;
+ font-weight: 500;
+}
+
+.link:hover {
+ text-decoration: underline;
+}
diff --git a/apps/web/src/pages/DashboardPage.module.css b/apps/web/src/pages/DashboardPage.module.css
new file mode 100644
index 0000000..b6921e0
--- /dev/null
+++ b/apps/web/src/pages/DashboardPage.module.css
@@ -0,0 +1,92 @@
+.container {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 2rem;
+}
+
+.header {
+ margin-bottom: 2rem;
+}
+
+.title {
+ font-size: 2rem;
+ font-weight: 700;
+ color: #1e293b;
+ margin: 0;
+}
+
+.subtitle {
+ color: #64748b;
+ margin: 0.5rem 0 0;
+ font-size: 1.125rem;
+}
+
+.grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+ gap: 1.5rem;
+}
+
+.card {
+ height: fit-content;
+}
+
+.cardTitle {
+ font-size: 1.125rem;
+ font-weight: 600;
+ color: #1e293b;
+ margin: 0;
+}
+
+.cardText {
+ color: #64748b;
+ margin: 0 0 1rem;
+}
+
+.stats {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 1rem;
+}
+
+.stat {
+ text-align: center;
+}
+
+.statValue {
+ display: block;
+ font-size: 2rem;
+ font-weight: 700;
+ color: #3b82f6;
+}
+
+.statLabel {
+ font-size: 0.875rem;
+ color: #64748b;
+}
+
+.list {
+ margin: 0;
+ padding-left: 1.25rem;
+ color: #374151;
+}
+
+.list li {
+ margin-bottom: 0.5rem;
+}
+
+.list li:last-child {
+ margin-bottom: 0;
+}
+
+.profileInfo {
+ color: #374151;
+}
+
+.profileInfo p {
+ margin: 0.5rem 0;
+}
+
+.profileInfo p:first-child {
+ margin-top: 0;
+}
diff --git a/apps/web/src/pages/DashboardPage.tsx b/apps/web/src/pages/DashboardPage.tsx
new file mode 100644
index 0000000..a832b15
--- /dev/null
+++ b/apps/web/src/pages/DashboardPage.tsx
@@ -0,0 +1,81 @@
+import { Link } from 'react-router-dom';
+import { useAuth } from '@/context';
+import { Card, CardHeader, CardContent, Button } from '@/components/ui';
+import styles from './DashboardPage.module.css';
+
+export function DashboardPage() {
+ const { user } = useAuth();
+
+ return (
+
+
+
+
+
+
+ 📋 Programs
+
+
+
+ View and manage your workout programs
+
+
+
+ View Programs
+
+
+
+
+
+
+
+ 🎯 Quick Stats
+
+
+
+
+ -
+ Active Programs
+
+
+ -
+ Total Sessions
+
+
+
+
+
+
+
+ 💡 Getting Started
+
+
+
+ Create your first program
+ Add cycles to structure your training
+ Design sessions with exercises
+ Track your sets and progress
+
+
+
+
+
+
+ 👤 Profile
+
+
+
+
Name: {user?.firstName} {user?.lastName}
+
Email: {user?.email}
+
+
+
+
+
+ );
+}
diff --git a/apps/web/src/pages/HomePage.module.css b/apps/web/src/pages/HomePage.module.css
new file mode 100644
index 0000000..4fbb943
--- /dev/null
+++ b/apps/web/src/pages/HomePage.module.css
@@ -0,0 +1,100 @@
+.container {
+ min-height: calc(100vh - 180px);
+}
+
+/* Hero Section */
+.hero {
+ text-align: center;
+ padding: 4rem 2rem 5rem;
+ background: linear-gradient(180deg, #f8fafc 0%, white 100%);
+}
+
+.heroTitle {
+ font-size: 3rem;
+ font-weight: 800;
+ color: #1e293b;
+ margin: 0 0 1.5rem;
+ line-height: 1.2;
+}
+
+.highlight {
+ color: #3b82f6;
+}
+
+.heroSubtitle {
+ font-size: 1.25rem;
+ color: #64748b;
+ max-width: 600px;
+ margin: 0 auto 2rem;
+ line-height: 1.6;
+}
+
+.heroCta {
+ display: flex;
+ gap: 1rem;
+ justify-content: center;
+ flex-wrap: wrap;
+}
+
+/* Features Section */
+.features {
+ padding: 4rem 2rem;
+ max-width: 1200px;
+ margin: 0 auto;
+}
+
+.featuresTitle {
+ text-align: center;
+ font-size: 2rem;
+ font-weight: 700;
+ color: #1e293b;
+ margin: 0 0 3rem;
+}
+
+.featuresGrid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+ gap: 2rem;
+}
+
+.feature {
+ background: white;
+ padding: 2rem;
+ border-radius: 12px;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+ border: 1px solid #e5e7eb;
+ text-align: center;
+}
+
+.featureIcon {
+ font-size: 2.5rem;
+ display: block;
+ margin-bottom: 1rem;
+}
+
+.feature h3 {
+ font-size: 1.25rem;
+ font-weight: 600;
+ color: #1e293b;
+ margin: 0 0 0.75rem;
+}
+
+.feature p {
+ color: #64748b;
+ margin: 0;
+ line-height: 1.5;
+}
+
+@media (max-width: 768px) {
+ .heroTitle {
+ font-size: 2rem;
+ }
+
+ .heroSubtitle {
+ font-size: 1rem;
+ }
+
+ .features {
+ padding: 2rem 1rem;
+ }
+}
diff --git a/apps/web/src/pages/HomePage.tsx b/apps/web/src/pages/HomePage.tsx
new file mode 100644
index 0000000..ab22c1e
--- /dev/null
+++ b/apps/web/src/pages/HomePage.tsx
@@ -0,0 +1,80 @@
+import { Link } from 'react-router-dom';
+import { useAuth } from '@/context';
+import { Button } from '@/components/ui';
+import styles from './HomePage.module.css';
+
+export function HomePage() {
+ const { isAuthenticated } = useAuth();
+
+ return (
+
+
+
+ Build Better Programs.
+
+ Achieve Better Results.
+
+
+ PwrProgram helps you design, manage, and track comprehensive workout
+ programs with a hierarchical structure for coaches and athletes.
+
+
+ {isAuthenticated ? (
+
+ Go to Dashboard
+
+ ) : (
+ <>
+
+ Get Started Free
+
+
+
+ Sign In
+
+
+ >
+ )}
+
+
+
+
+ Everything You Need
+
+
+
📊
+
Program Hierarchy
+
+ Structure your training with Programs → Cycles → Blocks →
+ Sessions → Exercises → Sets
+
+
+
+
🔒
+
Secure & Private
+
+ Your data is protected with industry-standard security and
+ encryption
+
+
+
+
📱
+
Easy to Use
+
+ Intuitive interface designed for quick program creation and
+ management
+
+
+
+
🤝
+
Coach Integration
+
+ Built-in support for coach-athlete relationships and program
+ sharing
+
+
+
+
+
+ );
+}
diff --git a/apps/web/src/pages/LoginPage.tsx b/apps/web/src/pages/LoginPage.tsx
new file mode 100644
index 0000000..7ca0e52
--- /dev/null
+++ b/apps/web/src/pages/LoginPage.tsx
@@ -0,0 +1,80 @@
+import { useState, type FormEvent } from 'react';
+import { Link, useNavigate, useLocation } from 'react-router-dom';
+import { useAuth } from '@/context';
+import { Button, Input, Card, CardHeader, CardContent } from '@/components/ui';
+import type { ApiError } from '@/types';
+import styles from './AuthPages.module.css';
+
+export function LoginPage() {
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [error, setError] = useState('');
+ const [isLoading, setIsLoading] = useState(false);
+
+ const { login } = useAuth();
+ const navigate = useNavigate();
+ const location = useLocation();
+
+ const from = (location.state as { from?: { pathname: string } })?.from?.pathname || '/dashboard';
+
+ const handleSubmit = async (e: FormEvent) => {
+ e.preventDefault();
+ setError('');
+ setIsLoading(true);
+
+ try {
+ await login(email, password);
+ navigate(from, { replace: true });
+ } catch (err) {
+ const apiError = err as ApiError;
+ setError(apiError.message || 'Login failed. Please try again.');
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ return (
+
+
+
+ Welcome Back
+ Sign in to your account
+
+
+
+
+
+ Don't have an account?{' '}
+
+ Sign up
+
+
+
+
+
+ );
+}
diff --git a/apps/web/src/pages/ProgramDetailPage.module.css b/apps/web/src/pages/ProgramDetailPage.module.css
new file mode 100644
index 0000000..0d91d59
--- /dev/null
+++ b/apps/web/src/pages/ProgramDetailPage.module.css
@@ -0,0 +1,107 @@
+.container {
+ max-width: 1000px;
+ margin: 0 auto;
+ padding: 2rem;
+}
+
+.loading {
+ text-align: center;
+ padding: 3rem;
+ color: #64748b;
+}
+
+.error {
+ padding: 1rem;
+ background: #fef2f2;
+ border: 1px solid #fee2e2;
+ border-radius: 8px;
+ color: #dc2626;
+ margin-bottom: 1.5rem;
+}
+
+.header {
+ margin-bottom: 2rem;
+}
+
+.backLink {
+ color: #3b82f6;
+ text-decoration: none;
+ font-size: 0.875rem;
+ display: inline-block;
+ margin-bottom: 1rem;
+}
+
+.backLink:hover {
+ text-decoration: underline;
+}
+
+.headerContent {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ gap: 1rem;
+}
+
+.title {
+ font-size: 2rem;
+ font-weight: 700;
+ color: #1e293b;
+ margin: 0;
+}
+
+.description {
+ color: #64748b;
+ margin: 0.5rem 0 0;
+ font-size: 1.125rem;
+}
+
+.actions {
+ display: flex;
+ gap: 0.75rem;
+}
+
+.infoCard {
+ margin-top: 1.5rem;
+}
+
+.sectionTitle {
+ margin: 0;
+ font-size: 1.25rem;
+ color: #1e293b;
+}
+
+.infoText {
+ color: #64748b;
+ margin: 0 0 1.5rem;
+}
+
+.structure {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
+ gap: 1rem;
+}
+
+.structureItem {
+ display: flex;
+ gap: 0.75rem;
+ padding: 1rem;
+ background: #f8fafc;
+ border-radius: 8px;
+ align-items: flex-start;
+}
+
+.structureIcon {
+ font-size: 1.5rem;
+}
+
+.structureItem h4 {
+ margin: 0 0 0.25rem;
+ color: #1e293b;
+ font-size: 0.9375rem;
+}
+
+.structureItem p {
+ margin: 0;
+ color: #64748b;
+ font-size: 0.8125rem;
+}
diff --git a/apps/web/src/pages/ProgramDetailPage.tsx b/apps/web/src/pages/ProgramDetailPage.tsx
new file mode 100644
index 0000000..320f16a
--- /dev/null
+++ b/apps/web/src/pages/ProgramDetailPage.tsx
@@ -0,0 +1,134 @@
+import { useState, useEffect } from 'react';
+import { useParams, useNavigate, Link } from 'react-router-dom';
+import { api } from '@/lib/api';
+import { Button, Card, CardContent, CardHeader } from '@/components/ui';
+import type { Program, ApiError } from '@/types';
+import styles from './ProgramDetailPage.module.css';
+
+export function ProgramDetailPage() {
+ const { id } = useParams<{ id: string }>();
+ const navigate = useNavigate();
+ const [program, setProgram] = useState(null);
+ const [isLoading, setIsLoading] = useState(true);
+ const [error, setError] = useState('');
+ const [isDeleting, setIsDeleting] = useState(false);
+
+ useEffect(() => {
+ if (id) {
+ loadProgram(id);
+ }
+ }, [id]);
+
+ const loadProgram = async (programId: string) => {
+ try {
+ setIsLoading(true);
+ const data = await api.getProgram(programId);
+ setProgram(data);
+ } catch (err) {
+ const apiError = err as ApiError;
+ setError(apiError.message || 'Failed to load program');
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const handleDelete = async () => {
+ if (!program || !window.confirm('Are you sure you want to delete this program?')) {
+ return;
+ }
+
+ try {
+ setIsDeleting(true);
+ await api.deleteProgram(program.id);
+ navigate('/programs');
+ } catch (err) {
+ const apiError = err as ApiError;
+ setError(apiError.message || 'Failed to delete program');
+ setIsDeleting(false);
+ }
+ };
+
+ if (isLoading) {
+ return (
+
+ );
+ }
+
+ if (error || !program) {
+ return (
+
+
{error || 'Program not found'}
+
+
← Back to Programs
+
+
+ );
+ }
+
+ return (
+
+
+
+ ← Back to Programs
+
+
+
+
{program.name}
+ {program.description && (
+
{program.description}
+ )}
+
+
+
+ Delete
+
+
+
+
+
+
+
+ Program Structure
+
+
+
+ This is where you can view and manage cycles, blocks, sessions, and exercises.
+ The full program structure management UI will be implemented as the application grows.
+
+
+
+
🔄
+
+
Cycles
+
Major training phases
+
+
+
+
📦
+
+
Blocks
+
Weekly training blocks
+
+
+
+
📅
+
+
Sessions
+
Individual workouts
+
+
+
+
💪
+
+
Exercises
+
Movements and sets
+
+
+
+
+
+
+ );
+}
diff --git a/apps/web/src/pages/ProgramsPage.module.css b/apps/web/src/pages/ProgramsPage.module.css
new file mode 100644
index 0000000..54af0ea
--- /dev/null
+++ b/apps/web/src/pages/ProgramsPage.module.css
@@ -0,0 +1,147 @@
+.container {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 2rem;
+}
+
+.header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 2rem;
+}
+
+.title {
+ font-size: 2rem;
+ font-weight: 700;
+ color: #1e293b;
+ margin: 0;
+}
+
+.subtitle {
+ color: #64748b;
+ margin: 0.5rem 0 0;
+}
+
+.error {
+ padding: 1rem;
+ background: #fef2f2;
+ border: 1px solid #fee2e2;
+ border-radius: 8px;
+ color: #dc2626;
+ margin-bottom: 1.5rem;
+}
+
+.loading {
+ text-align: center;
+ padding: 3rem;
+ color: #64748b;
+}
+
+.grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
+ gap: 1.5rem;
+}
+
+.programCard {
+ cursor: pointer;
+}
+
+.programName {
+ font-size: 1.25rem;
+ font-weight: 600;
+ color: #1e293b;
+ margin: 0 0 0.5rem;
+}
+
+.programDesc {
+ color: #64748b;
+ margin: 0;
+ font-size: 0.875rem;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+}
+
+.viewLink {
+ color: #3b82f6;
+ font-weight: 500;
+ font-size: 0.875rem;
+}
+
+/* Empty State */
+.emptyState {
+ max-width: 400px;
+ margin: 3rem auto;
+}
+
+.emptyContent {
+ text-align: center;
+ padding: 1rem;
+}
+
+.emptyIcon {
+ font-size: 3rem;
+ display: block;
+ margin-bottom: 1rem;
+}
+
+.emptyContent h3 {
+ margin: 0 0 0.5rem;
+ color: #1e293b;
+}
+
+.emptyContent p {
+ color: #64748b;
+ margin: 0 0 1.5rem;
+}
+
+/* Modal */
+.overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.5);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 200;
+ padding: 1rem;
+}
+
+.modal {
+ width: 100%;
+ max-width: 480px;
+}
+
+.modalTitle {
+ margin: 0;
+ font-size: 1.25rem;
+ color: #1e293b;
+}
+
+.form {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+}
+
+.formError {
+ padding: 0.75rem 1rem;
+ background: #fef2f2;
+ border: 1px solid #fee2e2;
+ border-radius: 8px;
+ color: #dc2626;
+ font-size: 0.875rem;
+}
+
+.formActions {
+ display: flex;
+ gap: 0.75rem;
+ justify-content: flex-end;
+ margin-top: 0.5rem;
+}
diff --git a/apps/web/src/pages/ProgramsPage.tsx b/apps/web/src/pages/ProgramsPage.tsx
new file mode 100644
index 0000000..bd9e512
--- /dev/null
+++ b/apps/web/src/pages/ProgramsPage.tsx
@@ -0,0 +1,168 @@
+import { useState, useEffect, type FormEvent } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { api } from '@/lib/api';
+import { Button, Input, Card, CardHeader, CardContent, CardFooter } from '@/components/ui';
+import type { Program, ApiError } from '@/types';
+import styles from './ProgramsPage.module.css';
+
+export function ProgramsPage() {
+ const [programs, setPrograms] = useState([]);
+ const [isLoading, setIsLoading] = useState(true);
+ const [error, setError] = useState('');
+ const [showCreateForm, setShowCreateForm] = useState(false);
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ loadPrograms();
+ }, []);
+
+ const loadPrograms = async () => {
+ try {
+ setIsLoading(true);
+ const response = await api.getPrograms();
+ setPrograms(response.data);
+ } catch (err) {
+ const apiError = err as ApiError;
+ setError(apiError.message || 'Failed to load programs');
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const handleProgramClick = (programId: string) => {
+ navigate(`/programs/${programId}`);
+ };
+
+ return (
+
+
+
+ {error &&
{error}
}
+
+ {showCreateForm && (
+
setShowCreateForm(false)}
+ onCreated={() => {
+ setShowCreateForm(false);
+ loadPrograms();
+ }}
+ />
+ )}
+
+ {isLoading ? (
+ Loading programs...
+ ) : programs.length === 0 ? (
+
+
+
+
📋
+
No programs yet
+
Create your first program to get started!
+
setShowCreateForm(true)}>
+ Create Program
+
+
+
+
+ ) : (
+
+ {programs.map((program) => (
+
handleProgramClick(program.id)}
+ >
+
+ {program.name}
+ {program.description && (
+ {program.description}
+ )}
+
+
+ View Details →
+
+
+ ))}
+
+ )}
+
+ );
+}
+
+interface CreateProgramFormProps {
+ onClose: () => void;
+ onCreated: () => void;
+}
+
+function CreateProgramForm({ onClose, onCreated }: CreateProgramFormProps) {
+ const [name, setName] = useState('');
+ const [description, setDescription] = useState('');
+ const [isLoading, setIsLoading] = useState(false);
+ const [error, setError] = useState('');
+
+ const handleSubmit = async (e: FormEvent) => {
+ e.preventDefault();
+ setError('');
+ setIsLoading(true);
+
+ try {
+ await api.createProgram({
+ name,
+ description: description || undefined,
+ });
+ onCreated();
+ } catch (err) {
+ const apiError = err as ApiError;
+ setError(apiError.message || 'Failed to create program');
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ return (
+
+
+
+ Create New Program
+
+
+
+
+
+
+ );
+}
diff --git a/apps/web/src/pages/RegisterPage.tsx b/apps/web/src/pages/RegisterPage.tsx
new file mode 100644
index 0000000..d450d14
--- /dev/null
+++ b/apps/web/src/pages/RegisterPage.tsx
@@ -0,0 +1,125 @@
+import { useState, type FormEvent } from 'react';
+import { Link, useNavigate } from 'react-router-dom';
+import { useAuth } from '@/context';
+import { Button, Input, Card, CardHeader, CardContent } from '@/components/ui';
+import type { ApiError } from '@/types';
+import styles from './AuthPages.module.css';
+
+export function RegisterPage() {
+ const [firstName, setFirstName] = useState('');
+ const [lastName, setLastName] = useState('');
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [confirmPassword, setConfirmPassword] = useState('');
+ const [error, setError] = useState('');
+ const [isLoading, setIsLoading] = useState(false);
+
+ const { register } = useAuth();
+ const navigate = useNavigate();
+
+ const handleSubmit = async (e: FormEvent) => {
+ e.preventDefault();
+ setError('');
+
+ if (password !== confirmPassword) {
+ setError('Passwords do not match');
+ return;
+ }
+
+ if (password.length < 6) {
+ setError('Password must be at least 6 characters');
+ return;
+ }
+
+ setIsLoading(true);
+
+ try {
+ await register({
+ email,
+ password,
+ firstName,
+ lastName: lastName || undefined,
+ });
+ navigate('/dashboard');
+ } catch (err) {
+ const apiError = err as ApiError;
+ setError(apiError.message || 'Registration failed. Please try again.');
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ return (
+
+
+
+ Create Account
+ Get started with PwrProgram
+
+
+
+
+
+ Already have an account?{' '}
+
+ Sign in
+
+
+
+
+
+ );
+}
diff --git a/apps/web/src/pages/index.ts b/apps/web/src/pages/index.ts
new file mode 100644
index 0000000..fa1060a
--- /dev/null
+++ b/apps/web/src/pages/index.ts
@@ -0,0 +1,6 @@
+export { HomePage } from './HomePage';
+export { LoginPage } from './LoginPage';
+export { RegisterPage } from './RegisterPage';
+export { DashboardPage } from './DashboardPage';
+export { ProgramsPage } from './ProgramsPage';
+export { ProgramDetailPage } from './ProgramDetailPage';
diff --git a/apps/web/src/types/index.ts b/apps/web/src/types/index.ts
new file mode 100644
index 0000000..6f4cc7c
--- /dev/null
+++ b/apps/web/src/types/index.ts
@@ -0,0 +1,115 @@
+// Types based on API responses
+
+export interface User {
+ id: string;
+ firstName: string;
+ lastName?: string;
+ email: string;
+ _links?: {
+ self: string;
+ programs: string;
+ coach?: string;
+ };
+}
+
+export interface Program {
+ id: string;
+ userId: string;
+ name: string;
+ description?: string;
+ coachId?: string;
+ _links?: {
+ self: string;
+ cycles: string;
+ coach?: string;
+ user: string;
+ };
+}
+
+export interface Cycle {
+ id: string;
+ programId: string;
+ name: string;
+ description?: string;
+ order: number;
+ _links?: {
+ self: string;
+ program: string;
+ blocks: string;
+ };
+}
+
+export interface Block {
+ id: string;
+ cycleId: string;
+ name: string;
+ description?: string;
+ order: number;
+ _links?: {
+ self: string;
+ cycle: string;
+ sessions: string;
+ };
+}
+
+export interface Session {
+ id: string;
+ blockId: string;
+ name: string;
+ description?: string;
+ order: number;
+ _links?: {
+ self: string;
+ block: string;
+ exercises: string;
+ };
+}
+
+export interface Exercise {
+ id: string;
+ sessionId: string;
+ name: string;
+ notes?: string;
+ order: number;
+ _links?: {
+ self: string;
+ session: string;
+ sets: string;
+ };
+}
+
+export interface Set {
+ id: string;
+ exerciseId: string;
+ reps?: number;
+ weight?: number;
+ rpe?: number;
+ notes?: string;
+ order: number;
+ _links?: {
+ self: string;
+ exercise: string;
+ };
+}
+
+export interface PaginatedResponse {
+ data: T[];
+ pagination: {
+ page: number;
+ limit: number;
+ total: number;
+ totalPages: number;
+ };
+}
+
+export interface ApiError {
+ message: string;
+ statusCode: number;
+ error?: string;
+}
+
+export interface AuthState {
+ user: User | null;
+ isAuthenticated: boolean;
+ isLoading: boolean;
+}
diff --git a/apps/web/tsconfig.app.json b/apps/web/tsconfig.app.json
new file mode 100644
index 0000000..0c521d5
--- /dev/null
+++ b/apps/web/tsconfig.app.json
@@ -0,0 +1,34 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+ "target": "ES2022",
+ "useDefineForClassFields": true,
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "types": ["vite/client"],
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Path aliases */
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["src/*"]
+ },
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["src"]
+}
diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json
new file mode 100644
index 0000000..1ffef60
--- /dev/null
+++ b/apps/web/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "files": [],
+ "references": [
+ { "path": "./tsconfig.app.json" },
+ { "path": "./tsconfig.node.json" }
+ ]
+}
diff --git a/apps/web/tsconfig.node.json b/apps/web/tsconfig.node.json
new file mode 100644
index 0000000..8a67f62
--- /dev/null
+++ b/apps/web/tsconfig.node.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+ "target": "ES2023",
+ "lib": ["ES2023"],
+ "module": "ESNext",
+ "types": ["node"],
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts
new file mode 100644
index 0000000..bc702d0
--- /dev/null
+++ b/apps/web/vite.config.ts
@@ -0,0 +1,22 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+import path from 'path'
+
+// https://vite.dev/config/
+export default defineConfig({
+ plugins: [react()],
+ resolve: {
+ alias: {
+ '@': path.resolve(__dirname, './src'),
+ },
+ },
+ server: {
+ port: 5173,
+ proxy: {
+ '/api': {
+ target: 'http://localhost:3000',
+ changeOrigin: true,
+ },
+ },
+ },
+})
diff --git a/package.json b/package.json
index 079cea1..e038697 100644
--- a/package.json
+++ b/package.json
@@ -9,15 +9,23 @@
"eslint-plugin-import": "^2.29.0"
},
"scripts": {
- "dev:api": "pnpm --filter pwrprogram devstart",
- "start:api": "pnpm --filter pwrprogram start",
-
+ "dev:api": "pnpm --filter @pwrprogram/api dev",
+ "dev:web": "pnpm --filter @pwrprogram/web dev",
+ "start:api": "pnpm --filter @pwrprogram/api start",
+ "build:web": "pnpm --filter @pwrprogram/web build",
"test": "pnpm -r --workspace-concurrency=1 test",
"test:watch": "pnpm -r test:watch",
"test:coverage": "pnpm -r --workspace-concurrency=1 test:coverage",
-
- "test:api": "pnpm --filter pwrprogram test",
- "test:api:watch": "pnpm --filter pwrprogram test:watch",
- "test:api:coverage": "pnpm --filter pwrprogram test:coverage"
+ "test:api": "pnpm --filter @pwrprogram/api test",
+ "test:api:watch": "pnpm --filter @pwrprogram/api test:watch",
+ "test:api:coverage": "pnpm --filter @pwrprogram/api test:coverage",
+ "lint:web": "pnpm --filter @pwrprogram/web lint"
+ },
+ "pnpm": {
+ "onlyBuiltDependencies": [
+ "bcrypt",
+ "sqlite3",
+ "unrs-resolver"
+ ]
}
-}
\ No newline at end of file
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8c916a0..ea4dfa0 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -13,16 +13,16 @@ importers:
version: 8.57.1
'@typescript-eslint/eslint-plugin':
specifier: ^7.18.0
- version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3)
+ version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)
'@typescript-eslint/parser':
specifier: ^7.18.0
- version: 7.18.0(eslint@8.57.1)(typescript@5.8.3)
+ version: 7.18.0(eslint@8.57.1)(typescript@5.9.3)
eslint:
specifier: ^8.57.0
version: 8.57.1
eslint-plugin-import:
specifier: ^2.29.0
- version: 2.32.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)
+ version: 2.32.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)
apps/pwrprogram:
dependencies:
@@ -139,6 +139,58 @@ importers:
specifier: 5.8.3
version: 5.8.3
+ apps/web:
+ dependencies:
+ '@pwrprogram/shared':
+ specifier: workspace:*
+ version: link:../../packages/shared
+ react:
+ specifier: ^19.2.0
+ version: 19.2.0
+ react-dom:
+ specifier: ^19.2.0
+ version: 19.2.0(react@19.2.0)
+ react-router-dom:
+ specifier: ^7.6.1
+ version: 7.9.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ devDependencies:
+ '@eslint/js':
+ specifier: ^9.39.1
+ version: 9.39.1
+ '@types/node':
+ specifier: ^24.10.1
+ version: 24.10.1
+ '@types/react':
+ specifier: ^19.2.5
+ version: 19.2.7
+ '@types/react-dom':
+ specifier: ^19.2.3
+ version: 19.2.3(@types/react@19.2.7)
+ '@vitejs/plugin-react':
+ specifier: ^5.1.1
+ version: 5.1.1(vite@7.2.4(@types/node@24.10.1)(yaml@2.8.1))
+ eslint:
+ specifier: ^9.39.1
+ version: 9.39.1
+ eslint-plugin-react-hooks:
+ specifier: ^7.0.1
+ version: 7.0.1(eslint@9.39.1)
+ eslint-plugin-react-refresh:
+ specifier: ^0.4.24
+ version: 0.4.24(eslint@9.39.1)
+ globals:
+ specifier: ^16.5.0
+ version: 16.5.0
+ typescript:
+ specifier: ~5.9.3
+ version: 5.9.3
+ typescript-eslint:
+ specifier: ^8.46.4
+ version: 8.48.0(eslint@9.39.1)(typescript@5.9.3)
+ vite:
+ specifier: ^7.2.4
+ version: 7.2.4(@types/node@24.10.1)(yaml@2.8.1)
+
packages/shared:
dependencies:
class-transformer:
@@ -166,10 +218,18 @@ packages:
resolution: {integrity: sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==}
engines: {node: '>=6.9.0'}
+ '@babel/core@7.28.5':
+ resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==}
+ engines: {node: '>=6.9.0'}
+
'@babel/generator@7.28.0':
resolution: {integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==}
engines: {node: '>=6.9.0'}
+ '@babel/generator@7.28.5':
+ resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helper-compilation-targets@7.27.2':
resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==}
engines: {node: '>=6.9.0'}
@@ -188,6 +248,12 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0
+ '@babel/helper-module-transforms@7.28.3':
+ resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
'@babel/helper-plugin-utils@7.27.1':
resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==}
engines: {node: '>=6.9.0'}
@@ -200,6 +266,10 @@ packages:
resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==}
engines: {node: '>=6.9.0'}
+ '@babel/helper-validator-identifier@7.28.5':
+ resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helper-validator-option@7.27.1':
resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
engines: {node: '>=6.9.0'}
@@ -208,11 +278,20 @@ packages:
resolution: {integrity: sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==}
engines: {node: '>=6.9.0'}
+ '@babel/helpers@7.28.4':
+ resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==}
+ engines: {node: '>=6.9.0'}
+
'@babel/parser@7.28.0':
resolution: {integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==}
engines: {node: '>=6.0.0'}
hasBin: true
+ '@babel/parser@7.28.5':
+ resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
'@babel/plugin-syntax-async-generators@7.8.4':
resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==}
peerDependencies:
@@ -304,6 +383,18 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
+ '@babel/plugin-transform-react-jsx-self@7.27.1':
+ resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-react-jsx-source@7.27.1':
+ resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
'@babel/template@7.27.2':
resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
engines: {node: '>=6.9.0'}
@@ -312,10 +403,18 @@ packages:
resolution: {integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==}
engines: {node: '>=6.9.0'}
+ '@babel/traverse@7.28.5':
+ resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==}
+ engines: {node: '>=6.9.0'}
+
'@babel/types@7.28.2':
resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==}
engines: {node: '>=6.9.0'}
+ '@babel/types@7.28.5':
+ resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==}
+ engines: {node: '>=6.9.0'}
+
'@bcoe/v8-coverage@0.2.3':
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
@@ -339,12 +438,174 @@ packages:
'@emnapi/wasi-threads@1.0.4':
resolution: {integrity: sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==}
+ '@esbuild/aix-ppc64@0.25.12':
+ resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@esbuild/android-arm64@0.25.12':
+ resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [android]
+
+ '@esbuild/android-arm@0.25.12':
+ resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-x64@0.25.12':
+ resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [android]
+
+ '@esbuild/darwin-arm64@0.25.12':
+ resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@esbuild/darwin-x64@0.25.12':
+ resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@esbuild/freebsd-arm64@0.25.12':
+ resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-x64@0.25.12':
+ resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@esbuild/linux-arm64@0.25.12':
+ resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@esbuild/linux-arm@0.25.12':
+ resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [linux]
+
+ '@esbuild/linux-ia32@0.25.12':
+ resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [linux]
+
+ '@esbuild/linux-loong64@0.25.12':
+ resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==}
+ engines: {node: '>=18'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-mips64el@0.25.12':
+ resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==}
+ engines: {node: '>=18'}
+ cpu: [mips64el]
+ os: [linux]
+
+ '@esbuild/linux-ppc64@0.25.12':
+ resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@esbuild/linux-riscv64@0.25.12':
+ resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==}
+ engines: {node: '>=18'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@esbuild/linux-s390x@0.25.12':
+ resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==}
+ engines: {node: '>=18'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@esbuild/linux-x64@0.25.12':
+ resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [linux]
+
+ '@esbuild/netbsd-arm64@0.25.12':
+ resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [netbsd]
+
+ '@esbuild/netbsd-x64@0.25.12':
+ resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@esbuild/openbsd-arm64@0.25.12':
+ resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openbsd]
+
+ '@esbuild/openbsd-x64@0.25.12':
+ resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@esbuild/openharmony-arm64@0.25.12':
+ resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@esbuild/sunos-x64@0.25.12':
+ resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@esbuild/win32-arm64@0.25.12':
+ resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-ia32@0.25.12':
+ resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@esbuild/win32-x64@0.25.12':
+ resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [win32]
+
'@eslint-community/eslint-utils@4.7.0':
resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
+ '@eslint-community/eslint-utils@4.9.0':
+ resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==}
+ 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.1':
resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==}
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
@@ -353,14 +614,26 @@ packages:
resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@eslint/config-array@0.21.1':
+ resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
'@eslint/config-helpers@0.3.1':
resolution: {integrity: sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==}
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.15.2':
resolution: {integrity: sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==}
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@2.1.4':
resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -377,14 +650,26 @@ packages:
resolution: {integrity: sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@eslint/js@9.39.1':
+ resolution: {integrity: sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
'@eslint/object-schema@2.1.6':
resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==}
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.3.5':
resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==}
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}
+
'@gar/promisify@1.1.3':
resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==}
@@ -514,6 +799,9 @@ packages:
'@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'}
@@ -565,6 +853,119 @@ packages:
resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==}
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
+ '@rolldown/pluginutils@1.0.0-beta.47':
+ resolution: {integrity: sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==}
+
+ '@rollup/rollup-android-arm-eabi@4.53.3':
+ resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==}
+ cpu: [arm]
+ os: [android]
+
+ '@rollup/rollup-android-arm64@4.53.3':
+ resolution: {integrity: sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==}
+ cpu: [arm64]
+ os: [android]
+
+ '@rollup/rollup-darwin-arm64@4.53.3':
+ resolution: {integrity: sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@rollup/rollup-darwin-x64@4.53.3':
+ resolution: {integrity: sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@rollup/rollup-freebsd-arm64@4.53.3':
+ resolution: {integrity: sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@rollup/rollup-freebsd-x64@4.53.3':
+ resolution: {integrity: sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.53.3':
+ resolution: {integrity: sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm-musleabihf@4.53.3':
+ resolution: {integrity: sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-gnu@4.53.3':
+ resolution: {integrity: sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-musl@4.53.3':
+ resolution: {integrity: sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-loong64-gnu@4.53.3':
+ resolution: {integrity: sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==}
+ cpu: [loong64]
+ os: [linux]
+
+ '@rollup/rollup-linux-ppc64-gnu@4.53.3':
+ resolution: {integrity: sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-gnu@4.53.3':
+ resolution: {integrity: sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-musl@4.53.3':
+ resolution: {integrity: sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-s390x-gnu@4.53.3':
+ resolution: {integrity: sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-gnu@4.53.3':
+ resolution: {integrity: sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-musl@4.53.3':
+ resolution: {integrity: sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-openharmony-arm64@4.53.3':
+ resolution: {integrity: sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@rollup/rollup-win32-arm64-msvc@4.53.3':
+ resolution: {integrity: sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@rollup/rollup-win32-ia32-msvc@4.53.3':
+ resolution: {integrity: sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==}
+ cpu: [ia32]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-gnu@4.53.3':
+ resolution: {integrity: sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==}
+ cpu: [x64]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-msvc@4.53.3':
+ resolution: {integrity: sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==}
+ cpu: [x64]
+ os: [win32]
+
'@rtsao/scc@1.1.0':
resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
@@ -674,6 +1075,9 @@ packages:
'@types/mime@1.3.5':
resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
+ '@types/node@24.10.1':
+ resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==}
+
'@types/node@24.2.1':
resolution: {integrity: sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ==}
@@ -683,6 +1087,14 @@ packages:
'@types/range-parser@1.2.7':
resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==}
+ '@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.7':
+ resolution: {integrity: sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==}
+
'@types/send@0.17.6':
resolution: {integrity: sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==}
@@ -724,6 +1136,14 @@ packages:
typescript:
optional: true
+ '@typescript-eslint/eslint-plugin@8.48.0':
+ resolution: {integrity: sha512-XxXP5tL1txl13YFtrECECQYeZjBZad4fyd3cFV4a19LkAY/bIp9fev3US4S5fDVV2JaYFiKAZ/GRTOLer+mbyQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ '@typescript-eslint/parser': ^8.48.0
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <6.0.0'
+
'@typescript-eslint/parser@7.18.0':
resolution: {integrity: sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==}
engines: {node: ^18.18.0 || >=20.0.0}
@@ -734,10 +1154,33 @@ packages:
typescript:
optional: true
+ '@typescript-eslint/parser@8.48.0':
+ resolution: {integrity: sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <6.0.0'
+
+ '@typescript-eslint/project-service@8.48.0':
+ resolution: {integrity: sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <6.0.0'
+
'@typescript-eslint/scope-manager@7.18.0':
resolution: {integrity: sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==}
engines: {node: ^18.18.0 || >=20.0.0}
+ '@typescript-eslint/scope-manager@8.48.0':
+ resolution: {integrity: sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@typescript-eslint/tsconfig-utils@8.48.0':
+ resolution: {integrity: sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <6.0.0'
+
'@typescript-eslint/type-utils@7.18.0':
resolution: {integrity: sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==}
engines: {node: ^18.18.0 || >=20.0.0}
@@ -748,10 +1191,21 @@ packages:
typescript:
optional: true
+ '@typescript-eslint/type-utils@8.48.0':
+ resolution: {integrity: sha512-zbeVaVqeXhhab6QNEKfK96Xyc7UQuoFWERhEnj3mLVnUWrQnv15cJNseUni7f3g557gm0e46LZ6IJ4NJVOgOpw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <6.0.0'
+
'@typescript-eslint/types@7.18.0':
resolution: {integrity: sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==}
engines: {node: ^18.18.0 || >=20.0.0}
+ '@typescript-eslint/types@8.48.0':
+ resolution: {integrity: sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
'@typescript-eslint/typescript-estree@7.18.0':
resolution: {integrity: sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==}
engines: {node: ^18.18.0 || >=20.0.0}
@@ -761,16 +1215,33 @@ packages:
typescript:
optional: true
+ '@typescript-eslint/typescript-estree@8.48.0':
+ resolution: {integrity: sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <6.0.0'
+
'@typescript-eslint/utils@7.18.0':
resolution: {integrity: sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==}
engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies:
eslint: ^8.56.0
+ '@typescript-eslint/utils@8.48.0':
+ resolution: {integrity: sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <6.0.0'
+
'@typescript-eslint/visitor-keys@7.18.0':
resolution: {integrity: sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==}
engines: {node: ^18.18.0 || >=20.0.0}
+ '@typescript-eslint/visitor-keys@8.48.0':
+ resolution: {integrity: sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
'@ungap/structured-clone@1.3.0':
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
@@ -869,6 +1340,12 @@ packages:
cpu: [x64]
os: [win32]
+ '@vitejs/plugin-react@5.1.1':
+ resolution: {integrity: sha512-WQfkSw0QbQ5aJ2CHYw23ZGkqnRwqKHD/KYsMeTkZzPT4Jcf0DcBxBtwMJxnu6E7oxw5+JC6ZAiePgh28uJ1HBA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ peerDependencies:
+ vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
+
abbrev@1.1.1:
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
@@ -1252,6 +1729,10 @@ packages:
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
engines: {node: '>= 0.6'}
+ cookie@1.0.2:
+ resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==}
+ engines: {node: '>=18'}
+
cookiejar@2.1.4:
resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==}
@@ -1266,6 +1747,9 @@ packages:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
+ csstype@3.2.3:
+ resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
+
data-view-buffer@1.0.2:
resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==}
engines: {node: '>= 0.4'}
@@ -1457,6 +1941,11 @@ packages:
resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==}
engines: {node: '>= 0.4'}
+ esbuild@0.25.12:
+ resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
+ engines: {node: '>=18'}
+ hasBin: true
+
escalade@3.2.0:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
@@ -1506,6 +1995,17 @@ packages:
'@typescript-eslint/parser':
optional: true
+ eslint-plugin-react-hooks@7.0.1:
+ resolution: {integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==}
+ 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
+
+ eslint-plugin-react-refresh@0.4.24:
+ resolution: {integrity: sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w==}
+ peerDependencies:
+ eslint: '>=8.40'
+
eslint-scope@7.2.2:
resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -1538,6 +2038,16 @@ packages:
jiti:
optional: true
+ eslint@9.39.1:
+ resolution: {integrity: sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==}
+ 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}
@@ -1623,6 +2133,15 @@ packages:
fb-watchman@2.0.2:
resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==}
+ fdir@6.5.0:
+ resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+
fecha@4.2.3:
resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==}
@@ -1776,6 +2295,10 @@ packages:
resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
engines: {node: '>=18'}
+ globals@16.5.0:
+ resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==}
+ engines: {node: '>=18'}
+
globalthis@1.0.4:
resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==}
engines: {node: '>= 0.4'}
@@ -1837,6 +2360,12 @@ packages:
resolution: {integrity: sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==}
engines: {node: '>=18.0.0'}
+ hermes-estree@0.25.1:
+ resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==}
+
+ hermes-parser@0.25.1:
+ resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==}
+
highlight.js@10.7.3:
resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==}
@@ -1879,6 +2408,10 @@ packages:
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'}
@@ -2433,6 +2966,11 @@ packages:
mz@2.7.0:
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
+ nanoid@3.3.11:
+ resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
napi-build-utils@2.0.0:
resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==}
@@ -2693,6 +3231,10 @@ packages:
resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
engines: {node: '>= 0.4'}
+ postcss@8.5.6:
+ resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
+ engines: {node: ^10 || ^12 || >=14}
+
postgres-array@2.0.0:
resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==}
engines: {node: '>=4'}
@@ -2779,9 +3321,39 @@ packages:
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
hasBin: true
+ react-dom@19.2.0:
+ resolution: {integrity: sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==}
+ peerDependencies:
+ react: ^19.2.0
+
react-is@18.3.1:
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
+ react-refresh@0.18.0:
+ resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==}
+ engines: {node: '>=0.10.0'}
+
+ react-router-dom@7.9.6:
+ resolution: {integrity: sha512-2MkC2XSXq6HjGcihnx1s0DBWQETI4mlis4Ux7YTLvP67xnGxCvq+BcCQSO81qQHVUTM1V53tl4iVVaY5sReCOA==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: '>=18'
+ react-dom: '>=18'
+
+ react-router@7.9.6:
+ resolution: {integrity: sha512-Y1tUp8clYRXpfPITyuifmSoE2vncSME18uVLgaqyxh9H35JWpIfzHo+9y3Fzh5odk/jxPW29IgLgzcdwxGqyNA==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: '>=18'
+ react-dom: '>=18'
+ peerDependenciesMeta:
+ react-dom:
+ optional: true
+
+ react@19.2.0:
+ resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==}
+ engines: {node: '>=0.10.0'}
+
readable-stream@3.6.2:
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
engines: {node: '>= 6'}
@@ -2838,6 +3410,11 @@ packages:
deprecated: Rimraf versions prior to v4 are no longer supported
hasBin: true
+ rollup@4.53.3:
+ resolution: {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==}
+ engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+ hasBin: true
+
router@2.2.0:
resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==}
engines: {node: '>= 18'}
@@ -2867,6 +3444,9 @@ packages:
safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+ 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
@@ -2887,6 +3467,9 @@ packages:
set-blocking@2.0.0:
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
+ set-cookie-parser@2.7.2:
+ resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==}
+
set-function-length@1.2.2:
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
engines: {node: '>= 0.4'}
@@ -2964,6 +3547,10 @@ packages:
resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==}
engines: {node: '>= 10.0.0', npm: '>= 3.0.0'}
+ source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
source-map-support@0.5.13:
resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==}
@@ -3124,6 +3711,10 @@ packages:
thenify@3.3.1:
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
+ tinyglobby@0.2.15:
+ resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
+ engines: {node: '>=12.0.0'}
+
tmpl@1.0.5:
resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==}
@@ -3153,6 +3744,12 @@ packages:
peerDependencies:
typescript: '>=4.2.0'
+ ts-api-utils@2.1.0:
+ resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==}
+ engines: {node: '>=18.12'}
+ peerDependencies:
+ typescript: '>=4.8.4'
+
ts-jest@29.4.1:
resolution: {integrity: sha512-SaeUtjfpg9Uqu8IbeDKtdaS0g8lS6FT6OzM3ezrDfErPJPHNDo/Ey+VFGP1bQIDfagYDLyRpd7O15XpG1Es2Uw==}
engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0}
@@ -3301,11 +3898,23 @@ packages:
typeorm-aurora-data-api-driver:
optional: true
+ typescript-eslint@8.48.0:
+ resolution: {integrity: sha512-fcKOvQD9GUn3Xw63EgiDqhvWJ5jsyZUaekl3KVpGsDJnN46WJTe3jWxtQP9lMZm1LJNkFLlTaWAxK2vUQR+cqw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <6.0.0'
+
typescript@5.8.3:
resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==}
engines: {node: '>=14.17'}
hasBin: true
+ typescript@5.9.3:
+ resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
uglify-js@3.19.3:
resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==}
engines: {node: '>=0.8.0'}
@@ -3325,6 +3934,9 @@ packages:
undici-types@7.10.0:
resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==}
+ undici-types@7.16.0:
+ resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
+
unique-filename@1.1.1:
resolution: {integrity: sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==}
@@ -3373,6 +3985,46 @@ packages:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'}
+ vite@7.2.4:
+ resolution: {integrity: sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': ^20.19.0 || >=22.12.0
+ jiti: '>=1.21.0'
+ less: ^4.0.0
+ lightningcss: ^1.21.0
+ sass: ^1.70.0
+ sass-embedded: ^1.70.0
+ stylus: '>=0.54.8'
+ sugarss: ^5.0.0
+ terser: ^5.16.0
+ tsx: ^4.8.1
+ yaml: ^2.4.2
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ jiti:
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
+
walker@1.0.8:
resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==}
@@ -3473,6 +4125,15 @@ packages:
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.1.13:
+ resolution: {integrity: sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==}
+
snapshots:
'@ampproject/remapping@2.3.0':
@@ -3508,6 +4169,26 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@babel/core@7.28.5':
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@babel/generator': 7.28.5
+ '@babel/helper-compilation-targets': 7.27.2
+ '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5)
+ '@babel/helpers': 7.28.4
+ '@babel/parser': 7.28.5
+ '@babel/template': 7.27.2
+ '@babel/traverse': 7.28.5
+ '@babel/types': 7.28.5
+ '@jridgewell/remapping': 2.3.5
+ convert-source-map: 2.0.0
+ debug: 4.4.1(supports-color@5.5.0)
+ gensync: 1.0.0-beta.2
+ json5: 2.2.3
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
'@babel/generator@7.28.0':
dependencies:
'@babel/parser': 7.28.0
@@ -3516,6 +4197,14 @@ snapshots:
'@jridgewell/trace-mapping': 0.3.30
jsesc: 3.1.0
+ '@babel/generator@7.28.5':
+ dependencies:
+ '@babel/parser': 7.28.5
+ '@babel/types': 7.28.5
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.30
+ jsesc: 3.1.0
+
'@babel/helper-compilation-targets@7.27.2':
dependencies:
'@babel/compat-data': 7.28.0
@@ -3542,12 +4231,23 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-module-imports': 7.27.1
+ '@babel/helper-validator-identifier': 7.27.1
+ '@babel/traverse': 7.28.5
+ transitivePeerDependencies:
+ - supports-color
+
'@babel/helper-plugin-utils@7.27.1': {}
'@babel/helper-string-parser@7.27.1': {}
'@babel/helper-validator-identifier@7.27.1': {}
+ '@babel/helper-validator-identifier@7.28.5': {}
+
'@babel/helper-validator-option@7.27.1': {}
'@babel/helpers@7.28.2':
@@ -3555,10 +4255,19 @@ snapshots:
'@babel/template': 7.27.2
'@babel/types': 7.28.2
+ '@babel/helpers@7.28.4':
+ dependencies:
+ '@babel/template': 7.27.2
+ '@babel/types': 7.28.5
+
'@babel/parser@7.28.0':
dependencies:
'@babel/types': 7.28.2
+ '@babel/parser@7.28.5':
+ dependencies:
+ '@babel/types': 7.28.5
+
'@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.0)':
dependencies:
'@babel/core': 7.28.0
@@ -3644,6 +4353,16 @@ snapshots:
'@babel/core': 7.28.0
'@babel/helper-plugin-utils': 7.27.1
+ '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-plugin-utils': 7.27.1
+
'@babel/template@7.27.2':
dependencies:
'@babel/code-frame': 7.27.1
@@ -3662,11 +4381,28 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@babel/traverse@7.28.5':
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@babel/generator': 7.28.5
+ '@babel/helper-globals': 7.28.0
+ '@babel/parser': 7.28.5
+ '@babel/template': 7.27.2
+ '@babel/types': 7.28.5
+ debug: 4.4.1(supports-color@5.5.0)
+ transitivePeerDependencies:
+ - supports-color
+
'@babel/types@7.28.2':
dependencies:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.27.1
+ '@babel/types@7.28.5':
+ dependencies:
+ '@babel/helper-string-parser': 7.27.1
+ '@babel/helper-validator-identifier': 7.28.5
+
'@bcoe/v8-coverage@0.2.3': {}
'@colors/colors@1.6.0': {}
@@ -3697,6 +4433,84 @@ snapshots:
tslib: 2.8.1
optional: true
+ '@esbuild/aix-ppc64@0.25.12':
+ optional: true
+
+ '@esbuild/android-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/android-arm@0.25.12':
+ optional: true
+
+ '@esbuild/android-x64@0.25.12':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/darwin-x64@0.25.12':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-arm@0.25.12':
+ optional: true
+
+ '@esbuild/linux-ia32@0.25.12':
+ optional: true
+
+ '@esbuild/linux-loong64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.25.12':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-s390x@0.25.12':
+ optional: true
+
+ '@esbuild/linux-x64@0.25.12':
+ optional: true
+
+ '@esbuild/netbsd-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.25.12':
+ optional: true
+
+ '@esbuild/openbsd-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.25.12':
+ optional: true
+
+ '@esbuild/openharmony-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/sunos-x64@0.25.12':
+ optional: true
+
+ '@esbuild/win32-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/win32-ia32@0.25.12':
+ optional: true
+
+ '@esbuild/win32-x64@0.25.12':
+ optional: true
+
'@eslint-community/eslint-utils@4.7.0(eslint@8.57.1)':
dependencies:
eslint: 8.57.1
@@ -3707,6 +4521,16 @@ snapshots:
eslint: 9.33.0
eslint-visitor-keys: 3.4.3
+ '@eslint-community/eslint-utils@4.7.0(eslint@9.39.1)':
+ dependencies:
+ eslint: 9.39.1
+ eslint-visitor-keys: 3.4.3
+
+ '@eslint-community/eslint-utils@4.9.0(eslint@9.39.1)':
+ dependencies:
+ eslint: 9.39.1
+ eslint-visitor-keys: 3.4.3
+
'@eslint-community/regexpp@4.12.1': {}
'@eslint/config-array@0.21.0':
@@ -3717,12 +4541,28 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@eslint/config-array@0.21.1':
+ dependencies:
+ '@eslint/object-schema': 2.1.7
+ debug: 4.4.1(supports-color@5.5.0)
+ minimatch: 3.1.2
+ transitivePeerDependencies:
+ - supports-color
+
'@eslint/config-helpers@0.3.1': {}
+ '@eslint/config-helpers@0.4.2':
+ dependencies:
+ '@eslint/core': 0.17.0
+
'@eslint/core@0.15.2':
dependencies:
'@types/json-schema': 7.0.15
+ '@eslint/core@0.17.0':
+ dependencies:
+ '@types/json-schema': 7.0.15
+
'@eslint/eslintrc@2.1.4':
dependencies:
ajv: 6.12.6
@@ -3755,13 +4595,22 @@ snapshots:
'@eslint/js@9.33.0': {}
+ '@eslint/js@9.39.1': {}
+
'@eslint/object-schema@2.1.6': {}
+ '@eslint/object-schema@2.1.7': {}
+
'@eslint/plugin-kit@0.3.5':
dependencies:
'@eslint/core': 0.15.2
levn: 0.4.1
+ '@eslint/plugin-kit@0.4.1':
+ dependencies:
+ '@eslint/core': 0.17.0
+ levn: 0.4.1
+
'@gar/promisify@1.1.3':
optional: true
@@ -3810,7 +4659,7 @@ snapshots:
'@jest/console@30.0.5':
dependencies:
'@jest/types': 30.0.5
- '@types/node': 24.2.1
+ '@types/node': 24.10.1
chalk: 4.1.2
jest-message-util: 30.0.5
jest-util: 30.0.5
@@ -3824,14 +4673,14 @@ snapshots:
'@jest/test-result': 30.0.5
'@jest/transform': 30.0.5
'@jest/types': 30.0.5
- '@types/node': 24.2.1
+ '@types/node': 24.10.1
ansi-escapes: 4.3.2
chalk: 4.1.2
ci-info: 4.3.0
exit-x: 0.2.2
graceful-fs: 4.2.11
jest-changed-files: 30.0.5
- jest-config: 30.0.5(@types/node@24.2.1)(ts-node@10.9.2(@types/node@24.2.1)(typescript@5.8.3))
+ jest-config: 30.0.5(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.2.1)(typescript@5.8.3))
jest-haste-map: 30.0.5
jest-message-util: 30.0.5
jest-regex-util: 30.0.1
@@ -3858,7 +4707,7 @@ snapshots:
dependencies:
'@jest/fake-timers': 30.0.5
'@jest/types': 30.0.5
- '@types/node': 24.2.1
+ '@types/node': 24.10.1
jest-mock: 30.0.5
'@jest/expect-utils@30.0.5':
@@ -3876,7 +4725,7 @@ snapshots:
dependencies:
'@jest/types': 30.0.5
'@sinonjs/fake-timers': 13.0.5
- '@types/node': 24.2.1
+ '@types/node': 24.10.1
jest-message-util: 30.0.5
jest-mock: 30.0.5
jest-util: 30.0.5
@@ -3894,7 +4743,7 @@ snapshots:
'@jest/pattern@30.0.1':
dependencies:
- '@types/node': 24.2.1
+ '@types/node': 24.10.1
jest-regex-util: 30.0.1
'@jest/reporters@30.0.5':
@@ -3905,7 +4754,7 @@ snapshots:
'@jest/transform': 30.0.5
'@jest/types': 30.0.5
'@jridgewell/trace-mapping': 0.3.30
- '@types/node': 24.2.1
+ '@types/node': 24.10.1
chalk: 4.1.2
collect-v8-coverage: 1.0.2
exit-x: 0.2.2
@@ -3982,7 +4831,7 @@ snapshots:
'@jest/schemas': 30.0.5
'@types/istanbul-lib-coverage': 2.0.6
'@types/istanbul-reports': 3.0.4
- '@types/node': 24.2.1
+ '@types/node': 24.10.1
'@types/yargs': 17.0.33
chalk: 4.1.2
@@ -3991,6 +4840,11 @@ snapshots:
'@jridgewell/sourcemap-codec': 1.5.5
'@jridgewell/trace-mapping': 0.3.30
+ '@jridgewell/remapping@2.3.5':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.30
+
'@jridgewell/resolve-uri@3.1.2': {}
'@jridgewell/sourcemap-codec@1.5.5': {}
@@ -4000,52 +4854,120 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
- '@jridgewell/trace-mapping@0.3.9':
- dependencies:
- '@jridgewell/resolve-uri': 3.1.2
- '@jridgewell/sourcemap-codec': 1.5.5
+ '@jridgewell/trace-mapping@0.3.9':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ '@napi-rs/wasm-runtime@0.2.12':
+ dependencies:
+ '@emnapi/core': 1.4.5
+ '@emnapi/runtime': 1.4.5
+ '@tybys/wasm-util': 0.10.0
+ optional: true
+
+ '@noble/hashes@1.8.0': {}
+
+ '@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.19.1
+
+ '@npmcli/fs@1.1.1':
+ dependencies:
+ '@gar/promisify': 1.1.3
+ semver: 7.7.2
+ optional: true
+
+ '@npmcli/move-file@1.1.2':
+ dependencies:
+ mkdirp: 1.0.4
+ rimraf: 3.0.2
+ optional: true
+
+ '@paralleldrive/cuid2@2.2.2':
+ dependencies:
+ '@noble/hashes': 1.8.0
+
+ '@pkgjs/parseargs@0.11.0':
+ optional: true
+
+ '@pkgr/core@0.2.9': {}
+
+ '@rolldown/pluginutils@1.0.0-beta.47': {}
+
+ '@rollup/rollup-android-arm-eabi@4.53.3':
+ optional: true
+
+ '@rollup/rollup-android-arm64@4.53.3':
+ optional: true
+
+ '@rollup/rollup-darwin-arm64@4.53.3':
+ optional: true
+
+ '@rollup/rollup-darwin-x64@4.53.3':
+ optional: true
+
+ '@rollup/rollup-freebsd-arm64@4.53.3':
+ optional: true
+
+ '@rollup/rollup-freebsd-x64@4.53.3':
+ optional: true
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.53.3':
+ optional: true
+
+ '@rollup/rollup-linux-arm-musleabihf@4.53.3':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-gnu@4.53.3':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-musl@4.53.3':
+ optional: true
+
+ '@rollup/rollup-linux-loong64-gnu@4.53.3':
+ optional: true
- '@napi-rs/wasm-runtime@0.2.12':
- dependencies:
- '@emnapi/core': 1.4.5
- '@emnapi/runtime': 1.4.5
- '@tybys/wasm-util': 0.10.0
+ '@rollup/rollup-linux-ppc64-gnu@4.53.3':
optional: true
- '@noble/hashes@1.8.0': {}
+ '@rollup/rollup-linux-riscv64-gnu@4.53.3':
+ optional: true
- '@nodelib/fs.scandir@2.1.5':
- dependencies:
- '@nodelib/fs.stat': 2.0.5
- run-parallel: 1.2.0
+ '@rollup/rollup-linux-riscv64-musl@4.53.3':
+ optional: true
- '@nodelib/fs.stat@2.0.5': {}
+ '@rollup/rollup-linux-s390x-gnu@4.53.3':
+ optional: true
- '@nodelib/fs.walk@1.2.8':
- dependencies:
- '@nodelib/fs.scandir': 2.1.5
- fastq: 1.19.1
+ '@rollup/rollup-linux-x64-gnu@4.53.3':
+ optional: true
- '@npmcli/fs@1.1.1':
- dependencies:
- '@gar/promisify': 1.1.3
- semver: 7.7.2
+ '@rollup/rollup-linux-x64-musl@4.53.3':
optional: true
- '@npmcli/move-file@1.1.2':
- dependencies:
- mkdirp: 1.0.4
- rimraf: 3.0.2
+ '@rollup/rollup-openharmony-arm64@4.53.3':
optional: true
- '@paralleldrive/cuid2@2.2.2':
- dependencies:
- '@noble/hashes': 1.8.0
+ '@rollup/rollup-win32-arm64-msvc@4.53.3':
+ optional: true
- '@pkgjs/parseargs@0.11.0':
+ '@rollup/rollup-win32-ia32-msvc@4.53.3':
optional: true
- '@pkgr/core@0.2.9': {}
+ '@rollup/rollup-win32-x64-gnu@4.53.3':
+ optional: true
+
+ '@rollup/rollup-win32-x64-msvc@4.53.3':
+ optional: true
'@rtsao/scc@1.1.0': {}
@@ -4112,11 +5034,11 @@ snapshots:
'@types/body-parser@1.19.6':
dependencies:
'@types/connect': 3.4.38
- '@types/node': 24.2.1
+ '@types/node': 24.10.1
'@types/connect@3.4.38':
dependencies:
- '@types/node': 24.2.1
+ '@types/node': 24.10.1
'@types/cookiejar@2.1.5': {}
@@ -4130,7 +5052,7 @@ snapshots:
'@types/express-serve-static-core@5.1.0':
dependencies:
- '@types/node': 24.2.1
+ '@types/node': 24.10.1
'@types/qs': 6.14.0
'@types/range-parser': 1.2.7
'@types/send': 1.2.1
@@ -4170,6 +5092,10 @@ snapshots:
'@types/mime@1.3.5': {}
+ '@types/node@24.10.1':
+ dependencies:
+ undici-types: 7.16.0
+
'@types/node@24.2.1':
dependencies:
undici-types: 7.10.0
@@ -4178,19 +5104,27 @@ snapshots:
'@types/range-parser@1.2.7': {}
+ '@types/react-dom@19.2.3(@types/react@19.2.7)':
+ dependencies:
+ '@types/react': 19.2.7
+
+ '@types/react@19.2.7':
+ dependencies:
+ csstype: 3.2.3
+
'@types/send@0.17.6':
dependencies:
'@types/mime': 1.3.5
- '@types/node': 24.2.1
+ '@types/node': 24.10.1
'@types/send@1.2.1':
dependencies:
- '@types/node': 24.2.1
+ '@types/node': 24.10.1
'@types/serve-static@1.15.10':
dependencies:
'@types/http-errors': 2.0.5
- '@types/node': 24.2.1
+ '@types/node': 24.10.1
'@types/send': 0.17.6
'@types/stack-utils@2.0.3': {}
@@ -4199,7 +5133,7 @@ snapshots:
dependencies:
'@types/cookiejar': 2.1.5
'@types/methods': 1.1.4
- '@types/node': 24.2.1
+ '@types/node': 24.10.1
form-data: 4.0.4
'@types/supertest@6.0.3':
@@ -4217,21 +5151,21 @@ snapshots:
dependencies:
'@types/yargs-parser': 21.0.3
- '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3)':
+ '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)':
dependencies:
'@eslint-community/regexpp': 4.12.1
- '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.8.3)
+ '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.9.3)
'@typescript-eslint/scope-manager': 7.18.0
- '@typescript-eslint/type-utils': 7.18.0(eslint@8.57.1)(typescript@5.8.3)
- '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.8.3)
+ '@typescript-eslint/type-utils': 7.18.0(eslint@8.57.1)(typescript@5.9.3)
+ '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 7.18.0
eslint: 8.57.1
graphemer: 1.4.0
ignore: 5.3.2
natural-compare: 1.4.0
- ts-api-utils: 1.4.3(typescript@5.8.3)
+ ts-api-utils: 1.4.3(typescript@5.9.3)
optionalDependencies:
- typescript: 5.8.3
+ typescript: 5.9.3
transitivePeerDependencies:
- supports-color
@@ -4253,16 +5187,33 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3)':
+ '@typescript-eslint/eslint-plugin@8.48.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)':
+ dependencies:
+ '@eslint-community/regexpp': 4.12.1
+ '@typescript-eslint/parser': 8.48.0(eslint@9.39.1)(typescript@5.9.3)
+ '@typescript-eslint/scope-manager': 8.48.0
+ '@typescript-eslint/type-utils': 8.48.0(eslint@9.39.1)(typescript@5.9.3)
+ '@typescript-eslint/utils': 8.48.0(eslint@9.39.1)(typescript@5.9.3)
+ '@typescript-eslint/visitor-keys': 8.48.0
+ eslint: 9.39.1
+ graphemer: 1.4.0
+ ignore: 7.0.5
+ natural-compare: 1.4.0
+ ts-api-utils: 2.1.0(typescript@5.9.3)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3)':
dependencies:
'@typescript-eslint/scope-manager': 7.18.0
'@typescript-eslint/types': 7.18.0
- '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.8.3)
+ '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 7.18.0
debug: 4.4.1(supports-color@5.5.0)
eslint: 8.57.1
optionalDependencies:
- typescript: 5.8.3
+ typescript: 5.9.3
transitivePeerDependencies:
- supports-color
@@ -4279,20 +5230,50 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@typescript-eslint/parser@8.48.0(eslint@9.39.1)(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/scope-manager': 8.48.0
+ '@typescript-eslint/types': 8.48.0
+ '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.9.3)
+ '@typescript-eslint/visitor-keys': 8.48.0
+ debug: 4.4.1(supports-color@5.5.0)
+ eslint: 9.39.1
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/project-service@8.48.0(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/tsconfig-utils': 8.48.0(typescript@5.9.3)
+ '@typescript-eslint/types': 8.48.0
+ debug: 4.4.1(supports-color@5.5.0)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
'@typescript-eslint/scope-manager@7.18.0':
dependencies:
'@typescript-eslint/types': 7.18.0
'@typescript-eslint/visitor-keys': 7.18.0
- '@typescript-eslint/type-utils@7.18.0(eslint@8.57.1)(typescript@5.8.3)':
+ '@typescript-eslint/scope-manager@8.48.0':
dependencies:
- '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.8.3)
- '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.8.3)
+ '@typescript-eslint/types': 8.48.0
+ '@typescript-eslint/visitor-keys': 8.48.0
+
+ '@typescript-eslint/tsconfig-utils@8.48.0(typescript@5.9.3)':
+ dependencies:
+ typescript: 5.9.3
+
+ '@typescript-eslint/type-utils@7.18.0(eslint@8.57.1)(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.9.3)
+ '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.9.3)
debug: 4.4.1(supports-color@5.5.0)
eslint: 8.57.1
- ts-api-utils: 1.4.3(typescript@5.8.3)
+ ts-api-utils: 1.4.3(typescript@5.9.3)
optionalDependencies:
- typescript: 5.8.3
+ typescript: 5.9.3
transitivePeerDependencies:
- supports-color
@@ -4308,8 +5289,22 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@typescript-eslint/type-utils@8.48.0(eslint@9.39.1)(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/types': 8.48.0
+ '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.9.3)
+ '@typescript-eslint/utils': 8.48.0(eslint@9.39.1)(typescript@5.9.3)
+ debug: 4.4.1(supports-color@5.5.0)
+ eslint: 9.39.1
+ ts-api-utils: 2.1.0(typescript@5.9.3)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
'@typescript-eslint/types@7.18.0': {}
+ '@typescript-eslint/types@8.48.0': {}
+
'@typescript-eslint/typescript-estree@7.18.0(typescript@5.8.3)':
dependencies:
'@typescript-eslint/types': 7.18.0
@@ -4325,12 +5320,42 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/utils@7.18.0(eslint@8.57.1)(typescript@5.8.3)':
+ '@typescript-eslint/typescript-estree@7.18.0(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/types': 7.18.0
+ '@typescript-eslint/visitor-keys': 7.18.0
+ debug: 4.4.1(supports-color@5.5.0)
+ globby: 11.1.0
+ is-glob: 4.0.3
+ minimatch: 9.0.5
+ semver: 7.7.2
+ ts-api-utils: 1.4.3(typescript@5.9.3)
+ optionalDependencies:
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/typescript-estree@8.48.0(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/project-service': 8.48.0(typescript@5.9.3)
+ '@typescript-eslint/tsconfig-utils': 8.48.0(typescript@5.9.3)
+ '@typescript-eslint/types': 8.48.0
+ '@typescript-eslint/visitor-keys': 8.48.0
+ debug: 4.4.1(supports-color@5.5.0)
+ minimatch: 9.0.5
+ semver: 7.7.2
+ tinyglobby: 0.2.15
+ ts-api-utils: 2.1.0(typescript@5.9.3)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/utils@7.18.0(eslint@8.57.1)(typescript@5.9.3)':
dependencies:
'@eslint-community/eslint-utils': 4.7.0(eslint@8.57.1)
'@typescript-eslint/scope-manager': 7.18.0
'@typescript-eslint/types': 7.18.0
- '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.8.3)
+ '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.9.3)
eslint: 8.57.1
transitivePeerDependencies:
- supports-color
@@ -4347,11 +5372,27 @@ snapshots:
- supports-color
- typescript
+ '@typescript-eslint/utils@8.48.0(eslint@9.39.1)(typescript@5.9.3)':
+ dependencies:
+ '@eslint-community/eslint-utils': 4.7.0(eslint@9.39.1)
+ '@typescript-eslint/scope-manager': 8.48.0
+ '@typescript-eslint/types': 8.48.0
+ '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.9.3)
+ eslint: 9.39.1
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
'@typescript-eslint/visitor-keys@7.18.0':
dependencies:
'@typescript-eslint/types': 7.18.0
eslint-visitor-keys: 3.4.3
+ '@typescript-eslint/visitor-keys@8.48.0':
+ dependencies:
+ '@typescript-eslint/types': 8.48.0
+ eslint-visitor-keys: 4.2.1
+
'@ungap/structured-clone@1.3.0': {}
'@unrs/resolver-binding-android-arm-eabi@1.11.1':
@@ -4413,6 +5454,18 @@ snapshots:
'@unrs/resolver-binding-win32-x64-msvc@1.11.1':
optional: true
+ '@vitejs/plugin-react@5.1.1(vite@7.2.4(@types/node@24.10.1)(yaml@2.8.1))':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5)
+ '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.5)
+ '@rolldown/pluginutils': 1.0.0-beta.47
+ '@types/babel__core': 7.20.5
+ react-refresh: 0.18.0
+ vite: 7.2.4(@types/node@24.10.1)(yaml@2.8.1)
+ transitivePeerDependencies:
+ - supports-color
+
abbrev@1.1.1:
optional: true
@@ -4881,6 +5934,8 @@ snapshots:
cookie@0.7.2: {}
+ cookie@1.0.2: {}
+
cookiejar@2.1.4: {}
cors@2.8.5:
@@ -4896,6 +5951,8 @@ snapshots:
shebang-command: 2.0.0
which: 2.0.2
+ csstype@3.2.3: {}
+
data-view-buffer@1.0.2:
dependencies:
call-bound: 1.0.4
@@ -5111,6 +6168,35 @@ snapshots:
is-date-object: 1.1.0
is-symbol: 1.1.1
+ esbuild@0.25.12:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.25.12
+ '@esbuild/android-arm': 0.25.12
+ '@esbuild/android-arm64': 0.25.12
+ '@esbuild/android-x64': 0.25.12
+ '@esbuild/darwin-arm64': 0.25.12
+ '@esbuild/darwin-x64': 0.25.12
+ '@esbuild/freebsd-arm64': 0.25.12
+ '@esbuild/freebsd-x64': 0.25.12
+ '@esbuild/linux-arm': 0.25.12
+ '@esbuild/linux-arm64': 0.25.12
+ '@esbuild/linux-ia32': 0.25.12
+ '@esbuild/linux-loong64': 0.25.12
+ '@esbuild/linux-mips64el': 0.25.12
+ '@esbuild/linux-ppc64': 0.25.12
+ '@esbuild/linux-riscv64': 0.25.12
+ '@esbuild/linux-s390x': 0.25.12
+ '@esbuild/linux-x64': 0.25.12
+ '@esbuild/netbsd-arm64': 0.25.12
+ '@esbuild/netbsd-x64': 0.25.12
+ '@esbuild/openbsd-arm64': 0.25.12
+ '@esbuild/openbsd-x64': 0.25.12
+ '@esbuild/openharmony-arm64': 0.25.12
+ '@esbuild/sunos-x64': 0.25.12
+ '@esbuild/win32-arm64': 0.25.12
+ '@esbuild/win32-ia32': 0.25.12
+ '@esbuild/win32-x64': 0.25.12
+
escalade@3.2.0: {}
escape-html@1.0.3: {}
@@ -5127,11 +6213,11 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-module-utils@2.12.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@8.57.1):
+ eslint-module-utils@2.12.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@8.57.1):
dependencies:
debug: 3.2.7
optionalDependencies:
- '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.8.3)
+ '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.9.3)
eslint: 8.57.1
eslint-import-resolver-node: 0.3.9
transitivePeerDependencies:
@@ -5147,7 +6233,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-plugin-import@2.32.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1):
+ eslint-plugin-import@2.32.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.9
@@ -5158,7 +6244,7 @@ snapshots:
doctrine: 2.1.0
eslint: 8.57.1
eslint-import-resolver-node: 0.3.9
- eslint-module-utils: 2.12.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@8.57.1)
+ eslint-module-utils: 2.12.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@8.57.1)
hasown: 2.0.2
is-core-module: 2.16.1
is-glob: 4.0.3
@@ -5170,7 +6256,7 @@ snapshots:
string.prototype.trimend: 1.0.9
tsconfig-paths: 3.15.0
optionalDependencies:
- '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.8.3)
+ '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.9.3)
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
@@ -5205,6 +6291,21 @@ snapshots:
- eslint-import-resolver-webpack
- supports-color
+ eslint-plugin-react-hooks@7.0.1(eslint@9.39.1):
+ dependencies:
+ '@babel/core': 7.28.0
+ '@babel/parser': 7.28.0
+ eslint: 9.39.1
+ hermes-parser: 0.25.1
+ zod: 4.1.13
+ zod-validation-error: 4.0.2(zod@4.1.13)
+ transitivePeerDependencies:
+ - supports-color
+
+ eslint-plugin-react-refresh@0.4.24(eslint@9.39.1):
+ dependencies:
+ eslint: 9.39.1
+
eslint-scope@7.2.2:
dependencies:
esrecurse: 4.3.0
@@ -5302,6 +6403,45 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ eslint@9.39.1:
+ dependencies:
+ '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1)
+ '@eslint-community/regexpp': 4.12.1
+ '@eslint/config-array': 0.21.1
+ '@eslint/config-helpers': 0.4.2
+ '@eslint/core': 0.17.0
+ '@eslint/eslintrc': 3.3.1
+ '@eslint/js': 9.39.1
+ '@eslint/plugin-kit': 0.4.1
+ '@humanfs/node': 0.16.6
+ '@humanwhocodes/module-importer': 1.0.1
+ '@humanwhocodes/retry': 0.4.3
+ '@types/estree': 1.0.8
+ ajv: 6.12.6
+ chalk: 4.1.2
+ cross-spawn: 7.0.6
+ debug: 4.4.1(supports-color@5.5.0)
+ escape-string-regexp: 4.0.0
+ eslint-scope: 8.4.0
+ eslint-visitor-keys: 4.2.1
+ espree: 10.4.0
+ esquery: 1.6.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.2
+ natural-compare: 1.4.0
+ optionator: 0.9.4
+ transitivePeerDependencies:
+ - supports-color
+
espree@10.4.0:
dependencies:
acorn: 8.15.0
@@ -5429,6 +6569,10 @@ snapshots:
dependencies:
bser: 2.1.1
+ fdir@6.5.0(picomatch@4.0.3):
+ optionalDependencies:
+ picomatch: 4.0.3
+
fecha@4.2.3: {}
file-entry-cache@6.0.1:
@@ -5610,6 +6754,8 @@ snapshots:
globals@14.0.0: {}
+ globals@16.5.0: {}
+
globalthis@1.0.4:
dependencies:
define-properties: 1.2.1
@@ -5668,6 +6814,12 @@ snapshots:
helmet@8.1.0: {}
+ hermes-estree@0.25.1: {}
+
+ hermes-parser@0.25.1:
+ dependencies:
+ hermes-estree: 0.25.1
+
highlight.js@10.7.3: {}
html-escaper@2.0.2: {}
@@ -5717,6 +6869,8 @@ snapshots:
ignore@5.3.2: {}
+ ignore@7.0.5: {}
+
import-fresh@3.3.1:
dependencies:
parent-module: 1.0.1
@@ -5933,7 +7087,7 @@ snapshots:
'@jest/expect': 30.0.5
'@jest/test-result': 30.0.5
'@jest/types': 30.0.5
- '@types/node': 24.2.1
+ '@types/node': 24.10.1
chalk: 4.1.2
co: 4.6.0
dedent: 1.6.0
@@ -5972,6 +7126,39 @@ snapshots:
- supports-color
- ts-node
+ jest-config@30.0.5(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.2.1)(typescript@5.8.3)):
+ dependencies:
+ '@babel/core': 7.28.0
+ '@jest/get-type': 30.0.1
+ '@jest/pattern': 30.0.1
+ '@jest/test-sequencer': 30.0.5
+ '@jest/types': 30.0.5
+ babel-jest: 30.0.5(@babel/core@7.28.0)
+ chalk: 4.1.2
+ ci-info: 4.3.0
+ deepmerge: 4.3.1
+ glob: 10.4.5
+ graceful-fs: 4.2.11
+ jest-circus: 30.0.5
+ jest-docblock: 30.0.1
+ jest-environment-node: 30.0.5
+ jest-regex-util: 30.0.1
+ jest-resolve: 30.0.5
+ jest-runner: 30.0.5
+ jest-util: 30.0.5
+ jest-validate: 30.0.5
+ micromatch: 4.0.8
+ parse-json: 5.2.0
+ pretty-format: 30.0.5
+ slash: 3.0.0
+ strip-json-comments: 3.1.1
+ optionalDependencies:
+ '@types/node': 24.10.1
+ ts-node: 10.9.2(@types/node@24.2.1)(typescript@5.8.3)
+ transitivePeerDependencies:
+ - babel-plugin-macros
+ - supports-color
+
jest-config@30.0.5(@types/node@24.2.1)(ts-node@10.9.2(@types/node@24.2.1)(typescript@5.8.3)):
dependencies:
'@babel/core': 7.28.0
@@ -6029,7 +7216,7 @@ snapshots:
'@jest/environment': 30.0.5
'@jest/fake-timers': 30.0.5
'@jest/types': 30.0.5
- '@types/node': 24.2.1
+ '@types/node': 24.10.1
jest-mock: 30.0.5
jest-util: 30.0.5
jest-validate: 30.0.5
@@ -6037,7 +7224,7 @@ snapshots:
jest-haste-map@30.0.5:
dependencies:
'@jest/types': 30.0.5
- '@types/node': 24.2.1
+ '@types/node': 24.10.1
anymatch: 3.1.3
fb-watchman: 2.0.2
graceful-fs: 4.2.11
@@ -6076,7 +7263,7 @@ snapshots:
jest-mock@30.0.5:
dependencies:
'@jest/types': 30.0.5
- '@types/node': 24.2.1
+ '@types/node': 24.10.1
jest-util: 30.0.5
jest-pnp-resolver@1.2.3(jest-resolve@30.0.5):
@@ -6110,7 +7297,7 @@ snapshots:
'@jest/test-result': 30.0.5
'@jest/transform': 30.0.5
'@jest/types': 30.0.5
- '@types/node': 24.2.1
+ '@types/node': 24.10.1
chalk: 4.1.2
emittery: 0.13.1
exit-x: 0.2.2
@@ -6139,7 +7326,7 @@ snapshots:
'@jest/test-result': 30.0.5
'@jest/transform': 30.0.5
'@jest/types': 30.0.5
- '@types/node': 24.2.1
+ '@types/node': 24.10.1
chalk: 4.1.2
cjs-module-lexer: 2.1.0
collect-v8-coverage: 1.0.2
@@ -6186,7 +7373,7 @@ snapshots:
jest-util@30.0.5:
dependencies:
'@jest/types': 30.0.5
- '@types/node': 24.2.1
+ '@types/node': 24.10.1
chalk: 4.1.2
ci-info: 4.3.0
graceful-fs: 4.2.11
@@ -6205,7 +7392,7 @@ snapshots:
dependencies:
'@jest/test-result': 30.0.5
'@jest/types': 30.0.5
- '@types/node': 24.2.1
+ '@types/node': 24.10.1
ansi-escapes: 4.3.2
chalk: 4.1.2
emittery: 0.13.1
@@ -6214,7 +7401,7 @@ snapshots:
jest-worker@30.0.5:
dependencies:
- '@types/node': 24.2.1
+ '@types/node': 24.10.1
'@ungap/structured-clone': 1.3.0
jest-util: 30.0.5
merge-stream: 2.0.0
@@ -6447,6 +7634,8 @@ snapshots:
object-assign: 4.1.1
thenify-all: 1.6.0
+ nanoid@3.3.11: {}
+
napi-build-utils@2.0.0: {}
napi-postinstall@0.3.3: {}
@@ -6707,6 +7896,12 @@ snapshots:
possible-typed-array-names@1.1.0: {}
+ postcss@8.5.6:
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
postgres-array@2.0.0: {}
postgres-bytea@1.0.0: {}
@@ -6791,8 +7986,31 @@ snapshots:
minimist: 1.2.8
strip-json-comments: 2.0.1
+ react-dom@19.2.0(react@19.2.0):
+ dependencies:
+ react: 19.2.0
+ scheduler: 0.27.0
+
react-is@18.3.1: {}
+ react-refresh@0.18.0: {}
+
+ react-router-dom@7.9.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
+ dependencies:
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ react-router: 7.9.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+
+ react-router@7.9.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
+ dependencies:
+ cookie: 1.0.2
+ react: 19.2.0
+ set-cookie-parser: 2.7.2
+ optionalDependencies:
+ react-dom: 19.2.0(react@19.2.0)
+
+ react@19.2.0: {}
+
readable-stream@3.6.2:
dependencies:
inherits: 2.0.4
@@ -6852,6 +8070,34 @@ snapshots:
dependencies:
glob: 7.2.3
+ rollup@4.53.3:
+ dependencies:
+ '@types/estree': 1.0.8
+ optionalDependencies:
+ '@rollup/rollup-android-arm-eabi': 4.53.3
+ '@rollup/rollup-android-arm64': 4.53.3
+ '@rollup/rollup-darwin-arm64': 4.53.3
+ '@rollup/rollup-darwin-x64': 4.53.3
+ '@rollup/rollup-freebsd-arm64': 4.53.3
+ '@rollup/rollup-freebsd-x64': 4.53.3
+ '@rollup/rollup-linux-arm-gnueabihf': 4.53.3
+ '@rollup/rollup-linux-arm-musleabihf': 4.53.3
+ '@rollup/rollup-linux-arm64-gnu': 4.53.3
+ '@rollup/rollup-linux-arm64-musl': 4.53.3
+ '@rollup/rollup-linux-loong64-gnu': 4.53.3
+ '@rollup/rollup-linux-ppc64-gnu': 4.53.3
+ '@rollup/rollup-linux-riscv64-gnu': 4.53.3
+ '@rollup/rollup-linux-riscv64-musl': 4.53.3
+ '@rollup/rollup-linux-s390x-gnu': 4.53.3
+ '@rollup/rollup-linux-x64-gnu': 4.53.3
+ '@rollup/rollup-linux-x64-musl': 4.53.3
+ '@rollup/rollup-openharmony-arm64': 4.53.3
+ '@rollup/rollup-win32-arm64-msvc': 4.53.3
+ '@rollup/rollup-win32-ia32-msvc': 4.53.3
+ '@rollup/rollup-win32-x64-gnu': 4.53.3
+ '@rollup/rollup-win32-x64-msvc': 4.53.3
+ fsevents: 2.3.3
+
router@2.2.0:
dependencies:
debug: 4.4.1(supports-color@5.5.0)
@@ -6891,6 +8137,8 @@ snapshots:
safer-buffer@2.1.2: {}
+ scheduler@0.27.0: {}
+
semver@6.3.1: {}
semver@7.7.2: {}
@@ -6923,6 +8171,8 @@ snapshots:
set-blocking@2.0.0:
optional: true
+ set-cookie-parser@2.7.2: {}
+
set-function-length@1.2.2:
dependencies:
define-data-property: 1.1.4
@@ -7023,6 +8273,8 @@ snapshots:
smart-buffer: 4.2.0
optional: true
+ source-map-js@1.2.1: {}
+
source-map-support@0.5.13:
dependencies:
buffer-from: 1.1.2
@@ -7218,6 +8470,11 @@ snapshots:
dependencies:
any-promise: 1.3.0
+ tinyglobby@0.2.15:
+ dependencies:
+ fdir: 6.5.0(picomatch@4.0.3)
+ picomatch: 4.0.3
+
tmpl@1.0.5: {}
to-buffer@1.2.1:
@@ -7240,6 +8497,14 @@ snapshots:
dependencies:
typescript: 5.8.3
+ ts-api-utils@1.4.3(typescript@5.9.3):
+ dependencies:
+ typescript: 5.9.3
+
+ ts-api-utils@2.1.0(typescript@5.9.3):
+ dependencies:
+ typescript: 5.9.3
+
ts-jest@29.4.1(@babel/core@7.28.0)(@jest/transform@30.0.5)(@jest/types@30.0.5)(babel-jest@30.0.5(@babel/core@7.28.0))(jest-util@30.0.5)(jest@30.0.5(@types/node@24.2.1)(ts-node@10.9.2(@types/node@24.2.1)(typescript@5.8.3)))(typescript@5.8.3):
dependencies:
bs-logger: 0.2.6
@@ -7366,8 +8631,21 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ typescript-eslint@8.48.0(eslint@9.39.1)(typescript@5.9.3):
+ dependencies:
+ '@typescript-eslint/eslint-plugin': 8.48.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)
+ '@typescript-eslint/parser': 8.48.0(eslint@9.39.1)(typescript@5.9.3)
+ '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.9.3)
+ '@typescript-eslint/utils': 8.48.0(eslint@9.39.1)(typescript@5.9.3)
+ eslint: 9.39.1
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
typescript@5.8.3: {}
+ typescript@5.9.3: {}
+
uglify-js@3.19.3:
optional: true
@@ -7386,6 +8664,8 @@ snapshots:
undici-types@7.10.0: {}
+ undici-types@7.16.0: {}
+
unique-filename@1.1.1:
dependencies:
unique-slug: 2.0.2
@@ -7450,6 +8730,19 @@ snapshots:
vary@1.1.2: {}
+ vite@7.2.4(@types/node@24.10.1)(yaml@2.8.1):
+ dependencies:
+ esbuild: 0.25.12
+ fdir: 6.5.0(picomatch@4.0.3)
+ picomatch: 4.0.3
+ postcss: 8.5.6
+ rollup: 4.53.3
+ tinyglobby: 0.2.15
+ optionalDependencies:
+ '@types/node': 24.10.1
+ fsevents: 2.3.3
+ yaml: 2.8.1
+
walker@1.0.8:
dependencies:
makeerror: 1.0.12
@@ -7584,3 +8877,9 @@ snapshots:
yn@3.1.1: {}
yocto-queue@0.1.0: {}
+
+ zod-validation-error@4.0.2(zod@4.1.13):
+ dependencies:
+ zod: 4.1.13
+
+ zod@4.1.13: {}
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index f3d8954..1dfbc20 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -1,3 +1,9 @@
-packages:
- - 'packages/*'
- - 'apps/*'
+packages:
+ - packages/*
+ - apps/*
+
+ignoredBuiltDependencies:
+ - '@scarf/scarf'
+ - bcrypt
+ - sqlite3
+ - unrs-resolver