diff --git a/examples/ui/msw-react-crud/.gitignore b/examples/ui/msw-react-crud/.gitignore new file mode 100644 index 000000000..5aa73bd33 --- /dev/null +++ b/examples/ui/msw-react-crud/.gitignore @@ -0,0 +1,31 @@ +# Dependencies +node_modules +pnpm-lock.yaml + +# Build outputs +dist +build +*.tsbuildinfo + +# Development +.vite +.cache + +# Environment +.env +.env.local + +# Editor +.vscode/* +!.vscode/extensions.json +.idea +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# MSW +public/mockServiceWorker.js diff --git a/examples/ui/msw-react-crud/CHANGELOG.md b/examples/ui/msw-react-crud/CHANGELOG.md new file mode 100644 index 000000000..619b660bf --- /dev/null +++ b/examples/ui/msw-react-crud/CHANGELOG.md @@ -0,0 +1,18 @@ +# @objectstack/example-msw-react-crud + +## 1.0.0 + +### Major Features + +- Initial release of MSW + React CRUD example +- Complete CRUD operations (Create, Read, Update, Delete) for tasks +- Integration with @objectstack/client for all API calls +- MSW browser worker setup for API mocking +- React components demonstrating best practices: + - TaskList component for displaying and managing tasks + - TaskForm component for creating and editing tasks + - TaskItem component for individual task display +- Comprehensive README with setup instructions and usage examples +- Full TypeScript support with proper type definitions +- Vite development server and build configuration +- Styled UI with modern CSS diff --git a/examples/ui/msw-react-crud/QUICKSTART.md b/examples/ui/msw-react-crud/QUICKSTART.md new file mode 100644 index 000000000..da4d802b9 --- /dev/null +++ b/examples/ui/msw-react-crud/QUICKSTART.md @@ -0,0 +1,171 @@ +# Quick Start Guide - Simplified MSW Integration + +This is the **simplified** version using `@objectstack/plugin-msw` that auto-mocks all API endpoints. No manual handler code required! + +## Prerequisites + +- Node.js 18 or later +- pnpm package manager + +## Step 1: Install Dependencies + +From the repository root: + +```bash +pnpm install +``` + +## Step 2: Build Required Packages + +Build the core packages: + +```bash +# Build all required packages +pnpm --filter @objectstack/spec build +pnpm --filter @objectstack/runtime build +pnpm --filter @objectstack/plugin-msw build +pnpm --filter @objectstack/client build +``` + +## Step 3: Initialize MSW + +The MSW service worker file should already be initialized: + +```bash +cd examples/ui/msw-react-crud +npx msw init public/ --save +``` + +## Step 4: Start the Development Server + +```bash +cd examples/ui/msw-react-crud +pnpm dev +``` + +The application will start on `http://localhost:3000` + +## ✨ What's Different? + +### Before (Manual Approach) ❌ + +You had to manually write 145+ lines of MSW handlers: + +```typescript +// src/mocks/browser.ts - OLD WAY +const handlers = [ + http.get('/api/v1/data/task', ({ request }) => { + // Manual pagination, filtering, sorting... + }), + http.post('/api/v1/data/task', async ({ request }) => { + // Manual ID generation, validation... + }), + // ... more manual handlers +]; +const worker = setupWorker(...handlers); +await worker.start(); +``` + +### After (Plugin Approach) ✅ + +Now just **3 lines** with auto-mocking: + +```typescript +// src/mocks/browser.ts - NEW WAY +const mswPlugin = new MSWPlugin({ baseUrl: '/api/v1' }); +const runtime = new ObjectStackKernel([appConfig, mswPlugin]); +await runtime.start(); // Auto-mocks ALL endpoints! +``` + +## 📦 How It Works + +1. **Define Your Data Model** in `objectstack.config.ts`: + ```typescript + const TaskObject = ObjectSchema.create({ + name: 'task', + fields: { + subject: Field.text({ required: true }), + priority: Field.number() + } + }); + ``` + +2. **Auto-Mock Everything**: The MSW plugin automatically mocks: + - ✅ Discovery endpoints + - ✅ Metadata endpoints + - ✅ All CRUD operations + - ✅ Query operations + - ✅ Pagination, sorting, filtering + +3. **Use ObjectStack Client** normally: + ```typescript + const client = new ObjectStackClient({ baseUrl: '/api/v1' }); + await client.data.create('task', { subject: 'New task' }); + ``` + +## 🎯 Test CRUD Operations + +Once running, you can: + +1. **Create** tasks using the form +2. **Read** tasks in the list +3. **Update** tasks by clicking "Edit" +4. **Delete** tasks by clicking "Delete" +5. **Toggle completion** status + +## 🔍 What Gets Auto-Mocked? + +The plugin automatically handles: + +| Endpoint | Description | +|----------|-------------| +| `GET /api/v1` | Discovery | +| `GET /api/v1/meta/*` | All metadata | +| `GET /api/v1/data/:object` | Find records | +| `GET /api/v1/data/:object/:id` | Get by ID | +| `POST /api/v1/data/:object` | Create | +| `PATCH /api/v1/data/:object/:id` | Update | +| `DELETE /api/v1/data/:object/:id` | Delete | + +**Zero manual code!** 🎉 + +## 🔧 Advanced: Custom Handlers + +Need custom logic? Easy: + +```typescript +const customHandlers = [ + http.get('/api/custom/hello', () => + HttpResponse.json({ message: 'Hello!' }) + ) +]; + +const mswPlugin = new MSWPlugin({ + customHandlers, // Add your custom handlers + baseUrl: '/api/v1' +}); +``` + +## Troubleshooting + +**MSW worker not starting?** +```bash +npx msw init public/ --save +``` + +**TypeScript errors?** +```bash +pnpm --filter @objectstack/spec build +pnpm --filter @objectstack/runtime build +pnpm --filter @objectstack/plugin-msw build +``` + +**404 errors?** +Check browser console for MSW startup message + +## 📚 Learn More + +- Full documentation: [README.md](./README.md) +- MSW Plugin: `packages/plugin-msw/README.md` +- Runtime: `packages/runtime/README.md` +- Client API: `packages/client/README.md` diff --git a/examples/ui/msw-react-crud/README.md b/examples/ui/msw-react-crud/README.md new file mode 100644 index 000000000..f06ba7ee0 --- /dev/null +++ b/examples/ui/msw-react-crud/README.md @@ -0,0 +1,239 @@ +# MSW + React CRUD Example + +This example demonstrates complete CRUD operations in a React application using **Mock Service Worker (MSW)** for API mocking and the **@objectstack/client** package for all data operations. + +## 🎯 Features + +- ✅ **Complete CRUD Operations**: Create, Read, Update, Delete tasks +- ✅ **ObjectStack Client Integration**: Uses official `@objectstack/client` for all API calls +- ✅ **MSW API Mocking**: All API requests are intercepted and mocked in the browser +- ✅ **React + TypeScript**: Modern React with full TypeScript support +- ✅ **Vite**: Fast development server and build tool +- ✅ **Best Practices**: Follows ObjectStack conventions and patterns + +## 📁 Project Structure + +``` +src/ +├── components/ +│ ├── TaskForm.tsx # Create/Update form component +│ ├── TaskItem.tsx # Single task display component +│ └── TaskList.tsx # Task list with read operations +├── mocks/ +│ └── browser.ts # MSW handlers and mock database +├── App.tsx # Main application component +├── App.css # Application styles +├── main.tsx # Entry point with MSW initialization +└── types.ts # TypeScript type definitions +``` + +## 🚀 Getting Started + +### Prerequisites + +- Node.js 18+ +- pnpm (package manager) + +### Installation + +```bash +# Install dependencies +pnpm install + +# Initialize MSW service worker (required for browser mode) +pnpm dlx msw init public/ --save +``` + +### Running the Application + +```bash +# Start development server +pnpm dev +``` + +The application will be available at `http://localhost:3000` + +### Building for Production + +```bash +# Build the application +pnpm build + +# Preview the production build +pnpm preview +``` + +## 📖 How It Works + +### 1. MSW Setup (`src/mocks/browser.ts`) + +MSW intercepts HTTP requests in the browser and returns mock data: + +```typescript +import { setupWorker } from 'msw/browser'; +import { http, HttpResponse } from 'msw'; + +// Define handlers matching ObjectStack API +const handlers = [ + http.get('/api/v1/data/task', () => { + return HttpResponse.json({ value: tasks, count: tasks.length }); + }), + + http.post('/api/v1/data/task', async ({ request }) => { + const body = await request.json(); + const newTask = { id: generateId(), ...body }; + return HttpResponse.json(newTask, { status: 201 }); + }), + + // ... more handlers +]; + +export const worker = setupWorker(...handlers); +``` + +### 2. ObjectStack Client Usage + +All components use the official `@objectstack/client` package: + +```typescript +import { ObjectStackClient } from '@objectstack/client'; + +// Initialize client +const client = new ObjectStackClient({ baseUrl: '/api/v1' }); +await client.connect(); + +// READ - Find all tasks +const result = await client.data.find('task', { + top: 100, + sort: ['priority'] +}); + +// CREATE - Create new task +const newTask = await client.data.create('task', { + subject: 'New task', + priority: 1 +}); + +// UPDATE - Update existing task +await client.data.update('task', taskId, { + isCompleted: true +}); + +// DELETE - Delete task +await client.data.delete('task', taskId); +``` + +### 3. React Components + +**TaskList Component** (`src/components/TaskList.tsx`) +- Fetches and displays all tasks +- Demonstrates READ operations +- Handles task deletion and status toggling + +**TaskForm Component** (`src/components/TaskForm.tsx`) +- Form for creating new tasks +- Form for editing existing tasks +- Demonstrates CREATE and UPDATE operations + +**TaskItem Component** (`src/components/TaskItem.tsx`) +- Displays individual task +- Provides edit and delete actions +- Shows task metadata (priority, completion status) + +## 🔌 API Endpoints Mocked + +The example mocks the following ObjectStack API endpoints: + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `GET` | `/api/v1` | Discovery endpoint | +| `GET` | `/api/v1/meta/object/task` | Get task object metadata | +| `GET` | `/api/v1/data/task` | Find/list all tasks | +| `GET` | `/api/v1/data/task/:id` | Get single task by ID | +| `POST` | `/api/v1/data/task` | Create new task | +| `PATCH` | `/api/v1/data/task/:id` | Update existing task | +| `DELETE` | `/api/v1/data/task/:id` | Delete task | + +## 🎨 UI Features + +- **Priority Indicators**: Color-coded priority levels (1-5) +- **Completion Status**: Checkbox to mark tasks as complete +- **Real-time Updates**: Automatic list refresh after CRUD operations +- **Responsive Design**: Works on desktop and mobile devices +- **Loading States**: Shows loading indicators during async operations +- **Error Handling**: Displays error messages for failed operations + +## 📚 Key Concepts + +### MSW (Mock Service Worker) + +MSW intercepts requests at the network level, making it ideal for: +- Development without a backend +- Testing components in isolation +- Demos and prototypes +- Offline development + +### ObjectStack Client + +The `@objectstack/client` provides a type-safe, consistent API for: +- Auto-discovery of server capabilities +- Metadata operations +- Data CRUD operations +- Query operations with filters, sorting, and pagination + +### Best Practices Demonstrated + +1. **Single Source of Truth**: All API calls go through ObjectStack Client +2. **Type Safety**: Full TypeScript support with proper interfaces +3. **Component Separation**: Clear separation between data fetching and presentation +4. **Error Handling**: Proper error handling and user feedback +5. **Loading States**: Visual feedback during async operations + +## 🔧 Customization + +### Adding New Fields + +1. Update the `Task` interface in `src/types.ts` +2. Update mock handlers in `src/mocks/browser.ts` +3. Update components to display/edit new fields + +### Changing Mock Data + +Edit the initial data in `src/mocks/browser.ts`: + +```typescript +const mockTasks = new Map([ + ['1', { id: '1', subject: 'Your task', priority: 1, ... }], + // Add more tasks... +]); +``` + +### Styling + +All styles are in `src/App.css`. The design uses CSS custom properties (variables) for easy theming. + +## 📦 Dependencies + +- **@objectstack/client** - Official ObjectStack client SDK +- **@objectstack/plugin-msw** - MSW integration for ObjectStack +- **react** - UI library +- **msw** - Mock Service Worker for API mocking +- **vite** - Build tool and dev server +- **typescript** - Type safety + +## 🤝 Related Examples + +- [`/examples/msw-demo`](../../../msw-demo) - MSW plugin integration examples +- [`/examples/todo`](../../../todo) - Todo app with ObjectStack Client +- [`/examples/ui/react-renderer`](../react-renderer) - React metadata renderer + +## 📖 Further Reading + +- [MSW Documentation](https://mswjs.io/) +- [ObjectStack Client API](../../packages/client) +- [ObjectStack Protocol Specification](../../packages/spec) +- [React Documentation](https://react.dev/) + +## 📝 License + +Apache-2.0 diff --git a/examples/ui/msw-react-crud/index.html b/examples/ui/msw-react-crud/index.html new file mode 100644 index 000000000..75d8f22ee --- /dev/null +++ b/examples/ui/msw-react-crud/index.html @@ -0,0 +1,13 @@ + + + + + + + ObjectStack MSW + React CRUD Example + + +
+ + + diff --git a/examples/ui/msw-react-crud/objectstack.config.ts b/examples/ui/msw-react-crud/objectstack.config.ts new file mode 100644 index 000000000..898c03053 --- /dev/null +++ b/examples/ui/msw-react-crud/objectstack.config.ts @@ -0,0 +1,57 @@ +/** + * Task Object Definition + */ +export const TaskObject = { + name: 'task', + label: 'Task', + description: 'Task management object', + icon: 'check-square', + titleFormat: '{subject}', + enable: { + apiEnabled: true, + trackHistory: false, + feeds: false, + activities: false, + mru: true, + }, + fields: { + id: { name: 'id', label: 'ID', type: 'text', required: true }, + subject: { name: 'subject', label: 'Subject', type: 'text', required: true }, + priority: { name: 'priority', label: 'Priority', type: 'number', defaultValue: 5 }, + isCompleted: { name: 'isCompleted', label: 'Completed', type: 'boolean', defaultValue: false }, + createdAt: { name: 'createdAt', label: 'Created At', type: 'datetime' } + } +}; + +/** + * App Configuration + */ +export default { + name: 'task_app', + label: 'Task Management', + description: 'MSW + React CRUD Example with ObjectStack', + version: '1.0.0', + icon: 'check-square', + branding: { + primaryColor: '#3b82f6', + logo: '/assets/logo.png', + }, + objects: [ + TaskObject + ], + navigation: [ + { + id: 'group_tasks', + type: 'group', + label: 'Tasks', + children: [ + { + id: 'nav_tasks', + type: 'object', + objectName: 'task', + label: 'My Tasks' + } + ] + } + ] +}; diff --git a/examples/ui/msw-react-crud/package.json b/examples/ui/msw-react-crud/package.json new file mode 100644 index 000000000..e85f86231 --- /dev/null +++ b/examples/ui/msw-react-crud/package.json @@ -0,0 +1,31 @@ +{ + "name": "@objectstack/example-msw-react-crud", + "version": "1.0.0", + "description": "Complete MSW integration example with React CRUD components using ObjectStack Client", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@objectstack/client": "workspace:*", + "@objectstack/spec": "workspace:*", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.1", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.4", + "msw": "^2.0.0", + "typescript": "^5.0.0", + "vite": "^5.4.11" + }, + "msw": { + "workerDirectory": [ + "public" + ] + } +} \ No newline at end of file diff --git a/examples/ui/msw-react-crud/src/App.css b/examples/ui/msw-react-crud/src/App.css new file mode 100644 index 000000000..506e411e1 --- /dev/null +++ b/examples/ui/msw-react-crud/src/App.css @@ -0,0 +1,367 @@ +/** + * App Styles + */ + +:root { + --primary-color: #2563eb; + --primary-hover: #1d4ed8; + --success-color: #10b981; + --danger-color: #ef4444; + --warning-color: #f59e0b; + --border-color: #e5e7eb; + --bg-color: #f9fafb; + --text-color: #111827; + --text-muted: #6b7280; + --shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1); +} + +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; + background-color: var(--bg-color); + color: var(--text-color); + line-height: 1.6; +} + +code { + background-color: #f1f5f9; + padding: 2px 6px; + border-radius: 4px; + font-family: 'Courier New', monospace; + font-size: 0.9em; +} + +/* App Container */ +.app-container { + max-width: 1200px; + margin: 0 auto; + padding: 2rem; +} + +/* Header */ +.app-header { + text-align: center; + margin-bottom: 3rem; + padding-bottom: 2rem; + border-bottom: 2px solid var(--border-color); +} + +.app-header h1 { + font-size: 2.5rem; + margin-bottom: 0.5rem; + color: var(--primary-color); +} + +.subtitle { + font-size: 1.1rem; + color: var(--text-muted); + margin-bottom: 1rem; +} + +.status-badge { + display: inline-flex; + align-items: center; + gap: 0.5rem; + background-color: var(--success-color); + color: white; + padding: 0.5rem 1rem; + border-radius: 20px; + font-size: 0.9rem; + font-weight: 500; +} + +.status-indicator { + width: 8px; + height: 8px; + background-color: white; + border-radius: 50%; + animation: pulse 2s infinite; +} + +@keyframes pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } +} + +/* Main Layout */ +.app-main { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 2rem; + margin-bottom: 3rem; +} + +@media (max-width: 768px) { + .app-main { + grid-template-columns: 1fr; + } +} + +/* Form Section */ +.form-section, .list-section { + background: white; + padding: 2rem; + border-radius: 8px; + box-shadow: var(--shadow); +} + +.task-form h2 { + margin-bottom: 1.5rem; + color: var(--primary-color); + font-size: 1.5rem; +} + +.form-group { + margin-bottom: 1.5rem; +} + +.form-group label { + display: block; + margin-bottom: 0.5rem; + font-weight: 500; + color: var(--text-color); +} + +.form-group input[type="text"], +.form-group select { + width: 100%; + padding: 0.75rem; + border: 1px solid var(--border-color); + border-radius: 6px; + font-size: 1rem; + transition: border-color 0.2s; +} + +.form-group input[type="text"]:focus, +.form-group select:focus { + outline: none; + border-color: var(--primary-color); + box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1); +} + +.checkbox-group label { + display: flex; + align-items: center; + gap: 0.5rem; + cursor: pointer; +} + +.checkbox-group input[type="checkbox"] { + width: 18px; + height: 18px; + cursor: pointer; +} + +.form-error { + background-color: #fee2e2; + color: var(--danger-color); + padding: 0.75rem; + border-radius: 6px; + margin-bottom: 1rem; + border-left: 4px solid var(--danger-color); +} + +.form-actions { + display: flex; + gap: 1rem; + margin-top: 1.5rem; +} + +/* Buttons */ +button { + padding: 0.75rem 1.5rem; + border: none; + border-radius: 6px; + font-size: 1rem; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; +} + +.btn-primary { + background-color: var(--primary-color); + color: white; +} + +.btn-primary:hover:not(:disabled) { + background-color: var(--primary-hover); + transform: translateY(-1px); + box-shadow: var(--shadow); +} + +.btn-secondary { + background-color: var(--border-color); + color: var(--text-color); +} + +.btn-secondary:hover:not(:disabled) { + background-color: #d1d5db; +} + +button:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.btn-edit { + background-color: var(--primary-color); + color: white; + padding: 0.5rem 1rem; + font-size: 0.9rem; +} + +.btn-edit:hover { + background-color: var(--primary-hover); +} + +.btn-delete { + background-color: var(--danger-color); + color: white; + padding: 0.5rem 1rem; + font-size: 0.9rem; +} + +.btn-delete:hover { + background-color: #dc2626; +} + +/* Task List */ +.task-list h2 { + margin-bottom: 1.5rem; + color: var(--primary-color); + font-size: 1.5rem; +} + +.tasks { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.task-item { + border: 1px solid var(--border-color); + border-radius: 8px; + padding: 1rem; + background-color: white; + transition: all 0.2s; +} + +.task-item:hover { + box-shadow: var(--shadow); + transform: translateY(-2px); +} + +.task-item.completed { + opacity: 0.7; + background-color: #f9fafb; +} + +.task-item.completed .task-subject { + text-decoration: line-through; + color: var(--text-muted); +} + +.task-content { + display: flex; + align-items: flex-start; + gap: 1rem; + margin-bottom: 1rem; +} + +.task-checkbox { + width: 20px; + height: 20px; + margin-top: 0.25rem; + cursor: pointer; +} + +.task-info { + flex: 1; +} + +.task-subject { + font-size: 1.1rem; + margin-bottom: 0.5rem; + color: var(--text-color); +} + +.task-meta { + display: flex; + gap: 1rem; + font-size: 0.9rem; + color: var(--text-muted); +} + +.task-priority { + padding: 0.25rem 0.75rem; + border-radius: 12px; + font-weight: 500; +} + +.priority-1 { + background-color: #fee2e2; + color: #991b1b; +} + +.priority-2 { + background-color: #fed7aa; + color: #9a3412; +} + +.priority-3 { + background-color: #fef3c7; + color: #92400e; +} + +.priority-4 { + background-color: #dbeafe; + color: #1e40af; +} + +.priority-5 { + background-color: #e0e7ff; + color: #3730a3; +} + +.task-actions { + display: flex; + gap: 0.5rem; + justify-content: flex-end; +} + +/* Loading & Empty States */ +.loading, .empty-state, .error { + text-align: center; + padding: 2rem; + color: var(--text-muted); +} + +.error { + color: var(--danger-color); +} + +.loading-container, .error-container { + text-align: center; + padding: 4rem 2rem; +} + +/* Footer */ +.app-footer { + text-align: center; + padding-top: 2rem; + border-top: 1px solid var(--border-color); + color: var(--text-muted); + font-size: 0.9rem; +} + +.tech-stack { + margin-top: 0.5rem; + font-size: 0.85rem; + color: var(--text-muted); +} diff --git a/examples/ui/msw-react-crud/src/App.tsx b/examples/ui/msw-react-crud/src/App.tsx new file mode 100644 index 000000000..017b534f2 --- /dev/null +++ b/examples/ui/msw-react-crud/src/App.tsx @@ -0,0 +1,127 @@ +/** + * App Component + * + * Main application component that demonstrates complete CRUD operations + * using ObjectStack Client with MSW for API mocking. + */ + +import { useState, useEffect } from 'react'; +import { ObjectStackClient } from '@objectstack/client'; +import type { Task } from './types'; +import { TaskForm } from './components/TaskForm'; +import { TaskList } from './components/TaskList'; +import './App.css'; + +export function App() { + const [client, setClient] = useState(null); + const [editingTask, setEditingTask] = useState(null); + const [refreshTrigger, setRefreshTrigger] = useState(0); + const [connected, setConnected] = useState(false); + const [error, setError] = useState(null); + + useEffect(() => { + initializeClient(); + }, []); + + async function initializeClient() { + try { + // Initialize ObjectStack Client pointing to our mocked API + const stackClient = new ObjectStackClient({ + baseUrl: '/api/v1' + }); + + // Connect to the server (will be intercepted by MSW) + await stackClient.connect(); + + setClient(stackClient); + setConnected(true); + console.log('✅ ObjectStack Client connected (via MSW)'); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to initialize client'); + console.error('Failed to initialize client:', err); + } + } + + function handleFormSuccess() { + setEditingTask(null); + // Trigger refresh of task list + setRefreshTrigger(prev => prev + 1); + } + + function handleEditTask(task: Task) { + setEditingTask(task); + // Scroll to form + window.scrollTo({ top: 0, behavior: 'smooth' }); + } + + function handleCancelEdit() { + setEditingTask(null); + } + + if (error) { + return ( +
+
+

Connection Error

+

{error}

+ +
+
+ ); + } + + if (!connected || !client) { + return ( +
+
+

Connecting to ObjectStack...

+

Initializing MSW and ObjectStack Client...

+
+
+ ); + } + + return ( +
+
+

📋 ObjectStack MSW + React CRUD Example

+

+ Complete CRUD operations using @objectstack/client with Mock Service Worker +

+
+ + MSW Active - All API calls are mocked +
+
+ +
+
+ +
+ +
+ +
+
+ +
+

+ This example demonstrates MSW integration with React components. + All API calls are intercepted and mocked in the browser. +

+

+ Tech: React + TypeScript + Vite + MSW + @objectstack/client +

+
+
+ ); +} diff --git a/examples/ui/msw-react-crud/src/components/TaskForm.tsx b/examples/ui/msw-react-crud/src/components/TaskForm.tsx new file mode 100644 index 000000000..88974aa04 --- /dev/null +++ b/examples/ui/msw-react-crud/src/components/TaskForm.tsx @@ -0,0 +1,164 @@ +/** + * TaskForm Component + * + * Form for creating and updating tasks. + * Demonstrates CREATE and UPDATE operations using @objectstack/client. + */ + +import { useState, useEffect } from 'react'; +import { ObjectStackClient } from '@objectstack/client'; +import type { Task, CreateTaskInput } from '../types'; + +interface TaskFormProps { + client: ObjectStackClient; + editingTask: Task | null; + onSuccess: () => void; + onCancel: () => void; +} + +export function TaskForm({ client, editingTask, onSuccess, onCancel }: TaskFormProps) { + const [subject, setSubject] = useState(''); + const [priority, setPriority] = useState(3); + const [isCompleted, setIsCompleted] = useState(false); + const [submitting, setSubmitting] = useState(false); + const [error, setError] = useState(null); + + // Populate form when editing + useEffect(() => { + if (editingTask) { + setSubject(editingTask.subject); + setPriority(editingTask.priority); + setIsCompleted(editingTask.isCompleted); + } else { + resetForm(); + } + }, [editingTask]); + + function resetForm() { + setSubject(''); + setPriority(3); + setIsCompleted(false); + setError(null); + } + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + + if (!subject.trim()) { + setError('Subject is required'); + return; + } + + setSubmitting(true); + setError(null); + + try { + if (editingTask) { + // UPDATE operation using ObjectStack Client + await client.data.update('task', editingTask.id, { + subject: subject.trim(), + priority, + isCompleted + }); + } else { + // CREATE operation using ObjectStack Client + const taskData: CreateTaskInput = { + subject: subject.trim(), + priority, + isCompleted + }; + + await client.data.create('task', taskData); + } + + resetForm(); + onSuccess(); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to save task'); + console.error('Error saving task:', err); + } finally { + setSubmitting(false); + } + } + + function handleCancel() { + resetForm(); + onCancel(); + } + + return ( +
+

{editingTask ? 'Edit Task' : 'Create New Task'}

+ +
+
+ + setSubject(e.target.value)} + placeholder="Enter task subject..." + disabled={submitting} + required + /> +
+ +
+ + +
+ +
+ +
+ + {error && ( +
+ {error} +
+ )} + +
+ + + {editingTask && ( + + )} +
+
+
+ ); +} diff --git a/examples/ui/msw-react-crud/src/components/TaskItem.tsx b/examples/ui/msw-react-crud/src/components/TaskItem.tsx new file mode 100644 index 000000000..5945d8180 --- /dev/null +++ b/examples/ui/msw-react-crud/src/components/TaskItem.tsx @@ -0,0 +1,52 @@ +/** + * TaskItem Component + * + * Displays a single task with actions (edit, delete, toggle complete). + */ + +import type { Task } from '../types'; + +interface TaskItemProps { + task: Task; + onEdit: () => void; + onDelete: () => void; + onToggleComplete: () => void; +} + +export function TaskItem({ task, onEdit, onDelete, onToggleComplete }: TaskItemProps) { + const priorityLabel = ['Critical', 'High', 'Medium', 'Low', 'Lowest'][task.priority - 1] || 'Unknown'; + const priorityClass = `priority-${task.priority}`; + + return ( +
+
+ +
+

{task.subject}

+
+ + Priority: {priorityLabel} ({task.priority}) + + + Created: {new Date(task.createdAt).toLocaleDateString()} + +
+
+
+ +
+ + +
+
+ ); +} diff --git a/examples/ui/msw-react-crud/src/components/TaskList.tsx b/examples/ui/msw-react-crud/src/components/TaskList.tsx new file mode 100644 index 000000000..c23c2dbd3 --- /dev/null +++ b/examples/ui/msw-react-crud/src/components/TaskList.tsx @@ -0,0 +1,112 @@ +/** + * TaskList Component + * + * Displays a list of tasks fetched from the ObjectStack API. + * Demonstrates READ operation using @objectstack/client. + */ + +import { useEffect, useState } from 'react'; +import { ObjectStackClient } from '@objectstack/client'; +import type { Task } from '../types'; +import { TaskItem } from './TaskItem'; + +interface TaskListProps { + client: ObjectStackClient; + onEdit: (task: Task) => void; + refreshTrigger: number; +} + +export function TaskList({ client, onEdit, refreshTrigger }: TaskListProps) { + const [tasks, setTasks] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + loadTasks(); + }, [refreshTrigger]); + + async function loadTasks() { + try { + setLoading(true); + setError(null); + + // Use ObjectStack Client to fetch tasks + const result = await client.data.find('task', { + top: 100, + sort: ['priority', '-createdAt'] + }); + + setTasks(result.value as Task[]); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to load tasks'); + console.error('Error loading tasks:', err); + } finally { + setLoading(false); + } + } + + async function handleDelete(id: string) { + try { + // Use ObjectStack Client to delete task + await client.data.delete('task', id); + + // Refresh the list + await loadTasks(); + } catch (err) { + alert('Failed to delete task: ' + (err instanceof Error ? err.message : 'Unknown error')); + } + } + + async function handleToggleComplete(task: Task) { + try { + // Use ObjectStack Client to update task + await client.data.update('task', task.id, { + isCompleted: !task.isCompleted + }); + + // Refresh the list + await loadTasks(); + } catch (err) { + alert('Failed to update task: ' + (err instanceof Error ? err.message : 'Unknown error')); + } + } + + if (loading) { + return ( +
+

Loading tasks...

+
+ ); + } + + if (error) { + return ( +
+

Error: {error}

+ +
+ ); + } + + return ( +
+

Tasks ({tasks.length})

+ + {tasks.length === 0 ? ( +

No tasks yet. Create one to get started!

+ ) : ( +
+ {tasks.map((task) => ( + onEdit(task)} + onDelete={() => handleDelete(task.id)} + onToggleComplete={() => handleToggleComplete(task)} + /> + ))} +
+ )} +
+ ); +} diff --git a/examples/ui/msw-react-crud/src/main.tsx b/examples/ui/msw-react-crud/src/main.tsx new file mode 100644 index 000000000..10bc318b3 --- /dev/null +++ b/examples/ui/msw-react-crud/src/main.tsx @@ -0,0 +1,25 @@ +/** + * Main Entry Point + * + * Initializes MSW and renders the React application. + */ + +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { App } from './App'; +import { startMockServer } from './mocks/browser'; + +// Start MSW before rendering the app +async function bootstrap() { + // Initialize Mock Service Worker + await startMockServer(); + + // Render the React app + ReactDOM.createRoot(document.getElementById('root')!).render( + + + + ); +} + +bootstrap(); diff --git a/examples/ui/msw-react-crud/src/mocks/browser.ts b/examples/ui/msw-react-crud/src/mocks/browser.ts new file mode 100644 index 000000000..16382dd4d --- /dev/null +++ b/examples/ui/msw-react-crud/src/mocks/browser.ts @@ -0,0 +1,68 @@ +/** + * MSW Browser Worker Setup + * + * Simplified setup using auto-generated handlers from objectstack.config.ts + * No runtime overhead - just pure MSW handlers generated from your config! + */ + +import { setupWorker } from 'msw/browser'; +import { createMockHandlers, seedData } from './createMockHandlers'; +import appConfig from '../../objectstack.config'; + +/** + * Start the MSW worker with auto-generated handlers + * + * This function: + * 1. Seeds initial data + * 2. Generates MSW handlers automatically from your config + * 3. Starts the MSW worker + */ +export async function startMockServer() { + // Seed initial task data + seedData('task', [ + { + id: '1', + subject: 'Complete MSW integration example', + priority: 1, + isCompleted: false, + createdAt: new Date().toISOString() + }, + { + id: '2', + subject: 'Test CRUD operations with React', + priority: 2, + isCompleted: false, + createdAt: new Date().toISOString() + }, + { + id: '3', + subject: 'Write documentation', + priority: 3, + isCompleted: true, + createdAt: new Date().toISOString() + } + ]); + + // Create metadata from config + const metadata = { + objects: (appConfig.objects || []).reduce((acc: any, obj: any) => { + acc[obj.name] = obj; + return acc; + }, {}) + }; + + // Create handlers from config + const handlers = createMockHandlers('/api/v1', metadata); + + // Start MSW worker + const worker = setupWorker(...handlers); + await worker.start({ + onUnhandledRequest: 'bypass', + }); + + console.log('[MSW] Auto-mocked API ready! All endpoints generated from objectstack.config.ts'); + console.log('[MSW] Objects:', Object.keys(metadata.objects)); + + return worker; +} + diff --git a/examples/ui/msw-react-crud/src/mocks/createMockHandlers.ts b/examples/ui/msw-react-crud/src/mocks/createMockHandlers.ts new file mode 100644 index 000000000..4793912be --- /dev/null +++ b/examples/ui/msw-react-crud/src/mocks/createMockHandlers.ts @@ -0,0 +1,199 @@ +/** + * Auto-generate MSW handlers from ObjectStack configuration + * + * This helper creates all necessary MSW handlers based on your + * objectstack.config.ts without requiring the full runtime. + */ + +import { http, HttpResponse } from 'msw'; + +// Simple in-memory store +const stores = new Map>(); + +let idCounters = new Map(); + +function getStore(objectName: string): Map { + if (!stores.has(objectName)) { + stores.set(objectName, new Map()); + } + return stores.get(objectName)!; +} + +function getNextId(objectName: string): string { + const current = idCounters.get(objectName) || 0; + const next = current + 1; + idCounters.set(objectName, next); + return String(next); +} + +/** + * Seed initial data for an object + */ +export function seedData(objectName: string, records: any[]) { + const store = getStore(objectName); + records.forEach((record) => { + const id = record.id || getNextId(objectName); + store.set(id, { ...record, id }); + }); + + // Initialize ID counter + const maxId = Math.max( + 0, + ...Array.from(store.values()).map(r => parseInt(r.id) || 0) + ); + idCounters.set(objectName, maxId); +} + +/** + * Create auto-mocked MSW handlers for ObjectStack API + */ +export function createMockHandlers(baseUrl: string = '/api/v1', metadata: any = {}) { + const discoveryResponse = { + name: 'ObjectStack Mock Server', + version: '1.0.0', + environment: 'development', + routes: { + discovery: `${baseUrl}`, + metadata: `${baseUrl}/meta`, + data: `${baseUrl}/data`, + ui: `${baseUrl}/ui` + }, + capabilities: { + search: true, + files: false + } + }; + + // Generate handlers for both correct paths and doubled paths (client compatibility) + const paths = [baseUrl, `${baseUrl}/api/v1`]; + + const handlers = []; + + for (const path of paths) { + handlers.push( + // Discovery endpoint + http.get(path, () => { + return HttpResponse.json(discoveryResponse); + }), + + // Meta endpoints + http.get(`${path}/meta`, () => { + return HttpResponse.json({ + data: [ + { type: 'object', href: `${baseUrl}/meta/objects`, count: Object.keys(metadata.objects || {}).length } + ] + }); + }), + + http.get(`${path}/meta/objects`, () => { + const objects = metadata.objects || {}; + return HttpResponse.json({ + data: Object.values(objects).map((obj: any) => ({ + name: obj.name, + label: obj.label, + description: obj.description, + path: `${baseUrl}/data/${obj.name}`, + self: `${baseUrl}/meta/object/${obj.name}` + })) + }); + }), + + http.get(`${path}/meta/object/:name`, ({ params }) => { + const objectName = params.name as string; + const obj = metadata.objects?.[objectName]; + + if (!obj) { + return HttpResponse.json({ error: 'Object not found' }, { status: 404 }); + } + + return HttpResponse.json(obj); + }), + + // Data: Find/List + http.get(`${path}/data/:object`, ({ params, request }) => { + const objectName = params.object as string; + const store = getStore(objectName); + const url = new URL(request.url); + + const top = parseInt(url.searchParams.get('top') || '100'); + const skip = parseInt(url.searchParams.get('$skip') || '0'); + + const records = Array.from(store.values()); + const paginatedRecords = records.slice(skip, skip + top); + + return HttpResponse.json({ + '@odata.context': `${baseUrl}/data/$metadata#${objectName}`, + value: paginatedRecords, + count: records.length + }); + }), + + // Data: Get by ID + http.get(`${path}/data/:object/:id`, ({ params }) => { + const objectName = params.object as string; + const id = params.id as string; + const store = getStore(objectName); + const record = store.get(id); + + if (!record) { + return HttpResponse.json({ error: 'Record not found' }, { status: 404 }); + } + + return HttpResponse.json(record); + }), + + // Data: Create + http.post(`${path}/data/:object`, async ({ params, request }) => { + const objectName = params.object as string; + const body = await request.json() as any; + const store = getStore(objectName); + + const id = body.id || getNextId(objectName); + const newRecord = { + ...body, + id, + createdAt: body.createdAt || new Date().toISOString() + }; + + store.set(id, newRecord); + + return HttpResponse.json(newRecord, { status: 201 }); + }), + + // Data: Update + http.patch(`${path}/data/:object/:id`, async ({ params, request }) => { + const objectName = params.object as string; + const id = params.id as string; + const updates = await request.json() as any; + const store = getStore(objectName); + const record = store.get(id); + + if (!record) { + return HttpResponse.json({ error: 'Record not found' }, { status: 404 }); + } + + const updatedRecord = { ...record, ...updates }; + store.set(id, updatedRecord); + + return HttpResponse.json(updatedRecord); + }), + + // Data: Delete + http.delete(`${path}/data/:object/:id`, ({ params }) => { + const objectName = params.object as string; + const id = params.id as string; + const store = getStore(objectName); + + if (!store.has(id)) { + return HttpResponse.json({ error: 'Record not found' }, { status: 404 }); + } + + store.delete(id); + + return HttpResponse.json({ success: true }, { status: 204 }); + }) + ); + } + + return handlers; +} diff --git a/examples/ui/msw-react-crud/src/types.ts b/examples/ui/msw-react-crud/src/types.ts new file mode 100644 index 000000000..789ed9aa1 --- /dev/null +++ b/examples/ui/msw-react-crud/src/types.ts @@ -0,0 +1,22 @@ +/** + * Task type definition + */ +export interface Task { + id: string; + subject: string; + priority: number; + isCompleted: boolean; + createdAt: string; +} + +export interface CreateTaskInput { + subject: string; + priority?: number; + isCompleted?: boolean; +} + +export interface UpdateTaskInput { + subject?: string; + priority?: number; + isCompleted?: boolean; +} diff --git a/examples/ui/msw-react-crud/tsconfig.json b/examples/ui/msw-react-crud/tsconfig.json new file mode 100644 index 000000000..a7fc6fbf2 --- /dev/null +++ b/examples/ui/msw-react-crud/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/examples/ui/msw-react-crud/tsconfig.node.json b/examples/ui/msw-react-crud/tsconfig.node.json new file mode 100644 index 000000000..42872c59f --- /dev/null +++ b/examples/ui/msw-react-crud/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/examples/ui/msw-react-crud/vite.config.ts b/examples/ui/msw-react-crud/vite.config.ts new file mode 100644 index 000000000..e7cace28c --- /dev/null +++ b/examples/ui/msw-react-crud/vite.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], + server: { + port: 3000, + }, + optimizeDeps: { + include: ['msw', 'msw/browser'] + } +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4b4024875..c80479e88 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -227,6 +227,115 @@ importers: specifier: ^5.0.0 version: 5.9.3 + examples/ui/custom-components: + dependencies: + '@objectstack/spec': + specifier: workspace:* + version: link:../../../packages/spec + lucide-react: + specifier: ^0.562.0 + version: 0.562.0(react@18.3.1) + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + devDependencies: + '@types/react': + specifier: ^18.3.1 + version: 18.3.27 + '@types/react-dom': + specifier: ^18.3.0 + version: 18.3.7(@types/react@18.3.27) + '@vitejs/plugin-react': + specifier: ^4.3.4 + version: 4.7.0(vite@5.4.21(@types/node@20.19.30)(lightningcss@1.30.2)) + typescript: + specifier: ^5.0.0 + version: 5.9.3 + vite: + specifier: ^5.4.11 + version: 5.4.21(@types/node@20.19.30)(lightningcss@1.30.2) + vitest: + specifier: ^2.1.8 + version: 2.1.9(@types/node@20.19.30)(lightningcss@1.30.2)(msw@2.12.7(@types/node@20.19.30)(typescript@5.9.3)) + + examples/ui/metadata-examples: + dependencies: + '@objectstack/spec': + specifier: workspace:* + version: link:../../../packages/spec + devDependencies: + typescript: + specifier: ^5.0.0 + version: 5.9.3 + + examples/ui/msw-react-crud: + dependencies: + '@objectstack/client': + specifier: workspace:* + version: link:../../../packages/client + '@objectstack/spec': + specifier: workspace:* + version: link:../../../packages/spec + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + devDependencies: + '@types/react': + specifier: ^18.3.1 + version: 18.3.27 + '@types/react-dom': + specifier: ^18.3.0 + version: 18.3.7(@types/react@18.3.27) + '@vitejs/plugin-react': + specifier: ^4.3.4 + version: 4.7.0(vite@5.4.21(@types/node@20.19.30)(lightningcss@1.30.2)) + msw: + specifier: ^2.0.0 + version: 2.12.7(@types/node@20.19.30)(typescript@5.9.3) + typescript: + specifier: ^5.0.0 + version: 5.9.3 + vite: + specifier: ^5.4.11 + version: 5.4.21(@types/node@20.19.30)(lightningcss@1.30.2) + + examples/ui/react-renderer: + dependencies: + '@objectstack/spec': + specifier: workspace:* + version: link:../../../packages/spec + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + devDependencies: + '@types/react': + specifier: ^18.3.1 + version: 18.3.27 + '@types/react-dom': + specifier: ^18.3.0 + version: 18.3.7(@types/react@18.3.27) + '@vitejs/plugin-react': + specifier: ^4.3.4 + version: 4.7.0(vite@5.4.21(@types/node@20.19.30)(lightningcss@1.30.2)) + typescript: + specifier: ^5.0.0 + version: 5.9.3 + vite: + specifier: ^5.4.11 + version: 5.4.21(@types/node@20.19.30)(lightningcss@1.30.2) + vitest: + specifier: ^2.1.8 + version: 2.1.9(@types/node@20.19.30)(lightningcss@1.30.2)(msw@2.12.7(@types/node@20.19.30)(typescript@5.9.3)) + packages/client: dependencies: '@objectstack/spec': @@ -368,6 +477,44 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} + '@babel/code-frame@7.28.6': + resolution: {integrity: sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.28.6': + resolution: {integrity: sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.28.6': + resolution: {integrity: sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.28.6': + resolution: {integrity: sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.27.1': resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} @@ -376,15 +523,43 @@ packages: 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'} + + '@babel/helpers@7.28.6': + resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} + engines: {node: '>=6.9.0'} + '@babel/parser@7.28.6': resolution: {integrity: sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==} engines: {node: '>=6.0.0'} hasBin: true + '@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/runtime@7.28.6': resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} engines: {node: '>=6.9.0'} + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.28.6': + resolution: {integrity: sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==} + engines: {node: '>=6.9.0'} + '@babel/types@7.28.6': resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==} engines: {node: '>=6.9.0'} @@ -1462,6 +1637,9 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + '@rolldown/pluginutils@1.0.0-beta.27': + resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + '@rollup/rollup-android-arm-eabi@4.55.2': resolution: {integrity: sha512-21J6xzayjy3O6NdnlO6aXi/urvSRjm6nCI6+nF6ra2YofKruGixN9kfT+dt55HVNwfDmpDHJcaS3JuP/boNnlA==} cpu: [arm] @@ -1728,6 +1906,18 @@ packages: '@tsconfig/node16@1.0.4': resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} @@ -1758,11 +1948,22 @@ packages: '@types/node@20.19.30': resolution: {integrity: sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==} + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + + '@types/react-dom@18.3.7': + resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} + peerDependencies: + '@types/react': ^18.0.0 + '@types/react-dom@19.2.3': resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} peerDependencies: '@types/react': ^19.2.0 + '@types/react@18.3.27': + resolution: {integrity: sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==} + '@types/react@19.2.8': resolution: {integrity: sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg==} @@ -1778,6 +1979,12 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + '@vitejs/plugin-react@4.7.0': + resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + '@vitest/coverage-v8@2.1.9': resolution: {integrity: sha512-Z2cOr0ksM00MpEfyVE8KXIYPEcBFxdbLSs56L8PO0QQMxt/6bDj45uQfxoc96v05KW3clk7vvgP0qfDit9DmfQ==} peerDependencies: @@ -2015,6 +2222,9 @@ packages: confbox@0.1.8: resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie@1.1.1: resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} engines: {node: '>=18'} @@ -2301,6 +2511,10 @@ packages: tailwindcss: optional: true + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} @@ -2470,6 +2684,9 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-tokens@9.0.1: resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} @@ -2481,6 +2698,16 @@ packages: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} @@ -2568,6 +2795,10 @@ packages: longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + loupe@2.3.7: resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} @@ -2577,6 +2808,9 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lucide-react@0.562.0: resolution: {integrity: sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw==} peerDependencies: @@ -2992,6 +3226,11 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + react-dom@19.2.3: resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==} peerDependencies: @@ -3006,6 +3245,10 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} + engines: {node: '>=0.10.0'} + react-remove-scroll-bar@2.3.8: resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} engines: {node: '>=10'} @@ -3036,6 +3279,10 @@ packages: '@types/react': optional: true + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + react@19.2.3: resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==} engines: {node: '>=0.10.0'} @@ -3121,12 +3368,19 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} scroll-into-view-if-needed@3.1.0: resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==} + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + semver@7.7.3: resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} engines: {node: '>=10'} @@ -3548,6 +3802,9 @@ packages: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -3591,16 +3848,115 @@ snapshots: '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 + '@babel/code-frame@7.28.6': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.28.6': {} + + '@babel/core@7.28.6': + dependencies: + '@babel/code-frame': 7.28.6 + '@babel/generator': 7.28.6 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) + '@babel/helpers': 7.28.6 + '@babel/parser': 7.28.6 + '@babel/template': 7.28.6 + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.28.6': + dependencies: + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.28.6 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.28.6': {} + '@babel/helper-string-parser@7.27.1': {} '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.6': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.28.6 + '@babel/parser@7.28.6': dependencies: '@babel/types': 7.28.6 + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/runtime@7.28.6': {} + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.28.6 + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 + + '@babel/traverse@7.28.6': + dependencies: + '@babel/code-frame': 7.28.6 + '@babel/generator': 7.28.6 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.6 + '@babel/template': 7.28.6 + '@babel/types': 7.28.6 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + '@babel/types@7.28.6': dependencies: '@babel/helper-string-parser': 7.27.1 @@ -4585,6 +4941,8 @@ snapshots: '@radix-ui/rect@1.1.1': {} + '@rolldown/pluginutils@1.0.0-beta.27': {} + '@rollup/rollup-android-arm-eabi@4.55.2': optional: true @@ -4797,6 +5155,27 @@ snapshots: '@tsconfig/node16@1.0.4': {} + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.28.6 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.28.6 + '@types/debug@4.1.12': dependencies: '@types/ms': 2.1.0 @@ -4827,10 +5206,21 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/prop-types@15.7.15': {} + + '@types/react-dom@18.3.7(@types/react@18.3.27)': + dependencies: + '@types/react': 18.3.27 + '@types/react-dom@19.2.3(@types/react@19.2.8)': dependencies: '@types/react': 19.2.8 + '@types/react@18.3.27': + dependencies: + '@types/prop-types': 15.7.15 + csstype: 3.2.3 + '@types/react@19.2.8': dependencies: csstype: 3.2.3 @@ -4843,6 +5233,18 @@ snapshots: '@ungap/structured-clone@1.3.0': {} + '@vitejs/plugin-react@4.7.0(vite@5.4.21(@types/node@20.19.30)(lightningcss@1.30.2))': + dependencies: + '@babel/core': 7.28.6 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.6) + '@rolldown/pluginutils': 1.0.0-beta.27 + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 5.4.21(@types/node@20.19.30)(lightningcss@1.30.2) + transitivePeerDependencies: + - supports-color + '@vitest/coverage-v8@2.1.9(vitest@2.1.9(@types/node@20.19.30)(lightningcss@1.30.2)(msw@2.12.7(@types/node@20.19.30)(typescript@5.9.3)))': dependencies: '@ampproject/remapping': 2.3.0 @@ -5086,6 +5488,8 @@ snapshots: confbox@0.1.8: {} + convert-source-map@2.0.0: {} + cookie@1.1.1: {} create-require@1.1.1: {} @@ -5417,6 +5821,8 @@ snapshots: transitivePeerDependencies: - '@types/react-dom' + gensync@1.0.0-beta.2: {} + get-caller-file@2.0.5: {} get-func-name@2.0.2: {} @@ -5608,6 +6014,8 @@ snapshots: jiti@2.6.1: {} + js-tokens@4.0.0: {} + js-tokens@9.0.1: {} js-yaml@3.14.2: @@ -5619,6 +6027,10 @@ snapshots: dependencies: argparse: 2.0.1 + jsesc@3.1.0: {} + + json5@2.2.3: {} + jsonfile@4.0.0: optionalDependencies: graceful-fs: 4.2.11 @@ -5685,6 +6097,10 @@ snapshots: longest-streak@3.1.0: {} + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + loupe@2.3.7: dependencies: get-func-name: 2.0.2 @@ -5693,6 +6109,14 @@ snapshots: lru-cache@10.4.3: {} + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lucide-react@0.562.0(react@18.3.1): + dependencies: + react: 18.3.1 + lucide-react@0.562.0(react@19.2.3): dependencies: react: 19.2.3 @@ -6369,6 +6793,12 @@ snapshots: queue-microtask@1.2.3: {} + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + react-dom@19.2.3(react@19.2.3): dependencies: react: 19.2.3 @@ -6381,6 +6811,8 @@ snapshots: react: 19.2.3 react-dom: 19.2.3(react@19.2.3) + react-refresh@0.17.0: {} + react-remove-scroll-bar@2.3.8(@types/react@19.2.8)(react@19.2.3): dependencies: react: 19.2.3 @@ -6408,6 +6840,10 @@ snapshots: optionalDependencies: '@types/react': 19.2.8 + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + react@19.2.3: {} read-yaml-file@1.1.0: @@ -6563,12 +6999,18 @@ snapshots: safer-buffer@2.1.2: {} + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + scheduler@0.27.0: {} scroll-into-view-if-needed@3.1.0: dependencies: compute-scroll-into-view: 3.1.1 + semver@6.3.1: {} + semver@7.7.3: {} server-only@0.0.1: {} @@ -7018,6 +7460,8 @@ snapshots: y18n@5.0.8: {} + yallist@3.1.1: {} + yargs-parser@21.1.1: {} yargs@17.7.2: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index f7b1a786a..78b146d8a 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -2,7 +2,9 @@ packages: - packages/* - apps/* - examples/* + - examples/ui/* onlyBuiltDependencies: - esbuild + - msw - sharp