diff --git a/ApiControllers/middlewares/token_middleware.py b/ApiControllers/middlewares/token_middleware.py
index ed9cf66..df25fc2 100644
--- a/ApiControllers/middlewares/token_middleware.py
+++ b/ApiControllers/middlewares/token_middleware.py
@@ -6,7 +6,6 @@ from Endpoints.routes import get_safe_endpoint_urls
async def token_middleware(request: Request, call_next):
-
base_url = request.url.path
safe_endpoints = [_[0] for _ in get_safe_endpoint_urls()]
if base_url in safe_endpoints:
diff --git a/ApiDefaults/config.py b/ApiDefaults/config.py
index 3e3cc2b..a9c0ea3 100644
--- a/ApiDefaults/config.py
+++ b/ApiDefaults/config.py
@@ -22,7 +22,7 @@ class Configs(BaseSettings):
EMAIL_HOST: str = ""
DATETIME_FORMAT: str = ""
FORGOT_LINK: str = ""
- ALLOW_ORIGINS: list = ["http://localhost:3000"]
+ ALLOW_ORIGINS: list = ["http://localhost:3000", "http://localhost:3001"]
VERSION: str = "0.1.001"
DESCRIPTION: str = ""
diff --git a/ApiServices/AuthService/config.py b/ApiServices/AuthService/config.py
index 7b81f87..1ec17d4 100644
--- a/ApiServices/AuthService/config.py
+++ b/ApiServices/AuthService/config.py
@@ -22,7 +22,7 @@ class Configs(BaseSettings):
EMAIL_HOST: str = ""
DATETIME_FORMAT: str = ""
FORGOT_LINK: str = ""
- ALLOW_ORIGINS: list = ["http://localhost:3000"]
+ ALLOW_ORIGINS: list = ["http://localhost:3000", "http://localhost:3001"]
VERSION: str = "0.1.001"
DESCRIPTION: str = ""
diff --git a/ApiServices/AuthService/endpoints/auth/route.py b/ApiServices/AuthService/endpoints/auth/route.py
index e2f819d..5c2779e 100644
--- a/ApiServices/AuthService/endpoints/auth/route.py
+++ b/ApiServices/AuthService/endpoints/auth/route.py
@@ -304,7 +304,7 @@ def authentication_token_check_post(
status_code=status.HTTP_406_NOT_ACCEPTABLE,
headers=headers,
)
- if AuthHandlers.LoginHandler.authentication_check_token_valid(access_token=token):
+ if AuthHandlers.LoginHandler.authentication_check_token_valid(domain=domain,access_token=token):
return JSONResponse(
content={"message": "MSG_0001"},
status_code=status.HTTP_202_ACCEPTED,
diff --git a/ApiServices/AuthService/events/auth/auth.py b/ApiServices/AuthService/events/auth/auth.py
index 5643f42..b1b460c 100644
--- a/ApiServices/AuthService/events/auth/auth.py
+++ b/ApiServices/AuthService/events/auth/auth.py
@@ -171,8 +171,18 @@ class LoginHandler:
access_key=data.access_key, db_session=db_session
)
+ other_domains_list, main_domain = [], ""
+ with mongo_handler.collection(f"{str(found_user.related_company)}*Domain") as collection:
+ result = collection.find_one({"user_uu_id": str(found_user.uu_id)})
+ if not result:
+ raise ValueError("EYS_00087")
+ other_domains_list = result.get("other_domains_list", [])
+ main_domain = result.get("main_domain", None)
+ if domain not in other_domains_list or not main_domain:
+ raise ValueError("EYS_00088")
+
if not user_handler.check_password_valid(
- domain=domain or "",
+ domain=main_domain,
id_=str(found_user.uu_id),
password=data.password,
password_hashed=found_user.hash_password,
@@ -233,6 +243,7 @@ class LoginHandler:
person_id=found_user.person_id,
person_uu_id=str(person.uu_id),
request=dict(request.headers),
+ domain_list=other_domains_list,
companies_uu_id_list=companies_uu_id_list,
companies_id_list=companies_id_list,
duty_uu_id_list=duty_uu_id_list,
@@ -286,13 +297,24 @@ class LoginHandler:
found_user = user_handler.check_user_exists(
access_key=data.access_key, db_session=db_session
)
+ other_domains_list, main_domain = [], ""
+ with mongo_handler.collection(f"{str(found_user.related_company)}*Domain") as collection:
+ result = collection.find_one({"user_uu_id": str(found_user.uu_id)})
+ if not result:
+ raise ValueError("EYS_00087")
+ other_domains_list = result.get("other_domains_list", [])
+ main_domain = result.get("main_domain", None)
+ if domain not in other_domains_list or not main_domain:
+ raise ValueError("EYS_00088")
+
if not user_handler.check_password_valid(
- domain=domain,
+ domain=main_domain,
id_=str(found_user.uu_id),
password=data.password,
password_hashed=found_user.hash_password,
):
raise ValueError("EYS_0005")
+
occupants_selection_dict: Dict[str, Any] = {}
living_spaces: list[BuildLivingSpace] = BuildLivingSpace.filter_all(
@@ -343,6 +365,7 @@ class LoginHandler:
user_id=found_user.id,
person_id=person.id,
person_uu_id=str(person.uu_id),
+ domain_list=other_domains_list,
request=dict(request.headers),
available_occupants=occupants_selection_dict,
).model_dump()
@@ -606,10 +629,17 @@ class LoginHandler:
)
@classmethod
- def authentication_check_token_valid(cls, access_token: str) -> bool:
+ def authentication_check_token_valid(cls, domain, access_token: str) -> bool:
redis_handler = RedisHandlers()
- if redis_handler.get_object_from_redis(access_token=access_token):
- return True
+ if auth_token := redis_handler.get_object_from_redis(access_token=access_token):
+ if auth_token.is_employee:
+ if domain not in auth_token.domain_list:
+ raise ValueError("EYS_00112")
+ return True
+ elif auth_token.is_occupant:
+ if domain not in auth_token.domain_list:
+ raise ValueError("EYS_00113")
+ return True
return False
diff --git a/ApiServices/AuthService/middlewares/token_middleware.py b/ApiServices/AuthService/middlewares/token_middleware.py
index fd9d93d..be084d0 100644
--- a/ApiServices/AuthService/middlewares/token_middleware.py
+++ b/ApiServices/AuthService/middlewares/token_middleware.py
@@ -5,6 +5,7 @@ from ..config import api_config
async def token_middleware(request: Request, call_next):
+ print("Token Middleware", dict(request.headers))
base_url = request.url.path
safe_endpoints = [_[0] for _ in get_safe_endpoint_urls()]
if base_url in safe_endpoints:
diff --git a/ApiServices/AuthService/validations/custom/token.py b/ApiServices/AuthService/validations/custom/token.py
index 92f0a4e..10142a2 100644
--- a/ApiServices/AuthService/validations/custom/token.py
+++ b/ApiServices/AuthService/validations/custom/token.py
@@ -35,6 +35,7 @@ class ApplicationToken(BaseModel):
person_uu_id: str
request: Optional[dict] = None # Request Info of Client
+ domain_list: Optional[list[str]] = None
expires_at: Optional[float] = None # Expiry timestamp
diff --git a/WebServices/client-frontend/setup-shadcn.sh b/WebServices/client-frontend/setup-shadcn.sh
new file mode 100755
index 0000000..b3c8d15
--- /dev/null
+++ b/WebServices/client-frontend/setup-shadcn.sh
@@ -0,0 +1,41 @@
+#!/bin/bash
+
+# Exit on error
+set -e
+
+echo "🚀 Setting up shadcn/ui components and dependencies..."
+
+# Install required dependencies for shadcn
+echo "📦 Installing required dependencies..."
+npm install tailwindcss-animate class-variance-authority clsx tailwind-merge --legacy-peer-deps
+
+# Initialize shadcn/ui (components.json already exists, so we'll skip this step)
+echo "✅ components.json already exists"
+
+# Set npm config to use legacy-peer-deps for all npm operations
+echo "🔧 Setting npm to use legacy-peer-deps..."
+npm config set legacy-peer-deps true
+
+# Install base components
+echo "🧩 Installing base shadcn/ui components..."
+npx shadcn@latest add button --yes
+npx shadcn@latest add form --yes
+npx shadcn@latest add input --yes
+npx shadcn@latest add label --yes
+npx shadcn@latest add select --yes
+npx shadcn@latest add checkbox --yes
+npx shadcn@latest add card --yes
+npx shadcn@latest add dialog --yes
+npx shadcn@latest add popover --yes
+npx shadcn@latest add sonner --yes
+npx shadcn@latest add table --yes
+npx shadcn@latest add pagination --yes
+npx shadcn@latest add calendar --yes
+npx shadcn@latest add date-picker --yes
+
+# Update any dependencies with legacy peer deps
+echo "🔄 Updating dependencies..."
+npm install --legacy-peer-deps
+
+echo "✨ Setup complete! You can now use shadcn/ui components in your project."
+echo "📚 Documentation: https://ui.shadcn.com/docs"
diff --git a/WebServices/client-frontend/src/app/page.tsx b/WebServices/client-frontend/src/app/page.tsx
index 01a89f7..89ed8a0 100644
--- a/WebServices/client-frontend/src/app/page.tsx
+++ b/WebServices/client-frontend/src/app/page.tsx
@@ -1,23 +1,95 @@
+"use server";
+
export default async function Home() {
- const result = await fetch("http://auth_service:8001/authentication/login", {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- language: "tr",
- domain: "evyos.com.tr",
- tz: "GMT+3",
- },
- body: JSON.stringify({
- access_key: "karatay.berkay.sup@evyos.com.tr",
- password: "string",
- remember_me: true,
- }),
+ // Server-side rendering
+ const currentDate = new Date().toLocaleString("tr-TR", {
+ timeZone: "Europe/Istanbul",
});
- const data = await result.json();
return (
-
- {JSON.stringify({ data: data?.data })}
+
+
+ {/* Welcome Banner */}
+
+
Welcome to EVYOS
+
Enterprise Management System
+
Server Time: {currentDate}
+
+
+ {/* Login Section */}
+
+
+
+ Login to Your Account
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
© {new Date().getFullYear()} EVYOS. All rights reserved.
+
+
+
);
}
diff --git a/WebServices/client-frontend/src/components/Pages/template/REACT_TEMPLATE_BLUEPRINT.md b/WebServices/client-frontend/src/components/Pages/template/REACT_TEMPLATE_BLUEPRINT.md
new file mode 100644
index 0000000..cabecca
--- /dev/null
+++ b/WebServices/client-frontend/src/components/Pages/template/REACT_TEMPLATE_BLUEPRINT.md
@@ -0,0 +1,111 @@
+# REACT_TEMPLATE_BLUEPRINT
+
+## Component Structure Reference
+
+```
+template/
+├── schema.ts # Data validation, field definitions, mock API
+├── hooks.ts # Custom hooks for data fetching and state management
+├── language.ts # Internationalization support
+├── app.tsx # Main orchestration component
+├── FormComponent.tsx # Form handling with validation
+├── SearchComponent.tsx # Field-specific search functionality
+├── DataDisplayComponent.tsx # Card-based data display
+├── ListInfoComponent.tsx # Pagination information and controls
+├── SortingComponent.tsx # Column sorting functionality
+├── PaginationToolsComponent.tsx # Page size and navigation controls
+└── ActionButtonsComponent.tsx # Context-aware action buttons
+```
+
+## Core Features
+
+- Zod schema validation
+- Field-specific search
+- Multi-field sorting (none/asc/desc)
+- Pagination with configurable page sizes
+- Form validation with error messages
+- Create/Update/View modes
+- Internationalization (en/tr)
+- Responsive design with Tailwind CSS
+
+## Implementation Details
+
+### Schema Structure
+```typescript
+// Base schema with validation
+export const DataSchema = z.object({
+ id: z.string().optional(),
+ title: z.string(),
+ description: z.string().optional(),
+ status: z.string(),
+ createdAt: z.string().or(z.date()).optional(),
+ updatedAt: z.string().or(z.date()).optional(),
+});
+
+// Field definitions with metadata
+const baseFieldDefinitions = {
+ id: { type: "text", group: "identificationInfo", label: "ID" },
+ title: { type: "text", group: "basicInfo", label: "Title" },
+ // Additional fields...
+};
+```
+
+### Hook Implementation
+```typescript
+export function usePaginatedData() {
+ // State management for data, pagination, loading, errors
+ // API fetching with debouncing
+ // Data validation with Zod
+ // Pagination state updates
+}
+```
+
+### Component Integration
+```typescript
+function TemplateApp({ lang = "en" }) {
+ // Data fetching with custom hook
+ // Mode management (list/create/view/update)
+ // Component orchestration
+}
+```
+
+## Build Instructions
+
+1. Create directory structure
+2. Implement schema with Zod validation
+3. Create custom hooks for data fetching
+4. Implement internationalization
+5. Build form component with validation
+6. Create search component with field selection
+7. Implement data display with cards
+8. Add pagination and sorting
+9. Connect components in main app
+10. Style with Tailwind CSS
+
+## Date Display Format
+
+Always use toLocaleString() instead of toLocaleDateString() for date formatting to show both date and time together.
+
+## API Integration
+
+Replace mock API functions with actual API calls:
+- GET for list view with pagination/sorting/filtering
+- POST for create operations
+- PUT/PATCH for update operations
+- GET with ID for view operations
+
+## Field Types Support
+
+- text: Standard text input
+- textarea: Multi-line text input
+- select: Dropdown selection
+- date: Date picker
+- checkbox: Boolean toggle
+- number: Numeric input
+
+## Customization Points
+
+- schema.ts: Data structure and validation
+- language.ts: Text translations
+- FormComponent.tsx: Field rendering logic
+- app.tsx: Component composition
diff --git a/WebServices/client-frontend/src/components/Pages/template/README.md b/WebServices/client-frontend/src/components/Pages/template/README.md
new file mode 100644
index 0000000..6cdd600
--- /dev/null
+++ b/WebServices/client-frontend/src/components/Pages/template/README.md
@@ -0,0 +1,90 @@
+# React Template Component
+
+This directory contains a reusable React template for building data management interfaces with complete CRUD (Create, Read, Update, Delete) functionality.
+
+## Overview
+
+The template provides a ready-to-use solution for displaying, searching, sorting, and editing data with a modern UI. It's designed to be easily customizable for different data types while maintaining a consistent user experience.
+
+## Key Features
+
+- **Data Display**: Card-based UI for showing data items
+- **Advanced Search**: Field-specific search capabilities
+- **Sorting**: Multi-field sorting with ascending/descending options
+- **Pagination**: Configurable page sizes and navigation
+- **Form Handling**: Create/Update/View modes with validation
+- **Internationalization**: Built-in support for multiple languages
+- **Responsive Design**: Works on desktop and mobile devices
+
+## Component Structure
+
+The template is organized into modular components that work together:
+
+### Core Files
+
+- **`schema.ts`**: Defines the data structure using Zod validation
+- **`hooks.ts`**: Custom hook for data fetching and pagination
+- **`language.ts`**: Internationalization support
+- **`app.tsx`**: Main component that orchestrates all other components
+
+### UI Components
+
+- **`FormComponent.tsx`**: Handles data entry and editing
+- **`SearchComponent.tsx`**: Provides field-specific search functionality
+- **`DataDisplayComponent.tsx`**: Displays data items in a card format
+- **`ListInfoComponent.tsx`**: Shows pagination information and controls
+- **`SortingComponent.tsx`**: Manages column sorting
+- **`PaginationToolsComponent.tsx`**: Controls for page size and navigation
+- **`ActionButtonsComponent.tsx`**: Context-aware action buttons
+
+## How to Use
+
+1. **Basic Usage**:
+ ```jsx
+ import TemplateApp from './template/app';
+
+ function MyPage() {
+ return
;
+ }
+ ```
+
+2. **Customizing Data Schema**:
+ - Modify `schema.ts` to match your data structure
+ - Update field definitions with appropriate types, labels, and grouping
+
+3. **API Integration**:
+ - Replace the mock `fetchData` function in `schema.ts` with your actual API call
+ - Implement the create/update API calls in `FormComponent.tsx`
+
+4. **Styling**:
+ - The template uses Tailwind CSS classes for styling
+ - Customize the appearance by modifying the CSS classes
+
+## Data Flow
+
+1. User interacts with the UI (search, sort, paginate)
+2. Component state updates trigger API calls
+3. Data is fetched, validated, and stored in state
+4. UI components render based on the current state
+5. Form submissions trigger API calls with validated data
+
+## Example Workflow
+
+1. **List View**: Users see paginated data with search and sort options
+2. **Create**: Click "Create New" to open a form for adding a new item
+3. **View**: Click "View" on an item to see all its details
+4. **Update**: Click "Update" on an item to edit its information
+
+## Customization Tips
+
+- **Adding Fields**: Update both the Zod schema and field definitions
+- **New Field Types**: Extend the form rendering logic in `FormComponent.tsx`
+- **Additional Languages**: Add new language entries to `language.ts`
+- **Custom Styling**: Modify the Tailwind classes in each component
+
+## Best Practices
+
+- Keep the schema definition in sync with your backend API
+- Use the field grouping feature to organize complex forms
+- Leverage the built-in validation to ensure data quality
+- Consider adding custom field types for specific data needs
diff --git a/WebServices/management-frontend/components.json b/WebServices/management-frontend/components.json
new file mode 100644
index 0000000..ffe928f
--- /dev/null
+++ b/WebServices/management-frontend/components.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "new-york",
+ "rsc": true,
+ "tsx": true,
+ "tailwind": {
+ "config": "",
+ "css": "src/app/globals.css",
+ "baseColor": "neutral",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils",
+ "ui": "@/components/ui",
+ "lib": "@/lib",
+ "hooks": "@/hooks"
+ },
+ "iconLibrary": "lucide"
+}
\ No newline at end of file
diff --git a/WebServices/management-frontend/package-lock.json b/WebServices/management-frontend/package-lock.json
index 3edf030..5745e4f 100644
--- a/WebServices/management-frontend/package-lock.json
+++ b/WebServices/management-frontend/package-lock.json
@@ -8,9 +8,29 @@
"name": "management-frontend",
"version": "0.1.0",
"dependencies": {
+ "@hookform/resolvers": "^5.0.1",
+ "@radix-ui/react-checkbox": "^1.2.3",
+ "@radix-ui/react-dialog": "^1.1.11",
+ "@radix-ui/react-label": "^2.1.4",
+ "@radix-ui/react-popover": "^1.1.11",
+ "@radix-ui/react-select": "^2.2.2",
+ "@radix-ui/react-slot": "^1.2.0",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "date-fns": "^4.1.0",
+ "flatpickr": "^4.6.13",
+ "lucide-react": "^0.503.0",
"next": "15.2.4",
+ "next-crypto": "^1.0.8",
+ "next-themes": "^0.4.6",
"react": "^19.0.0",
- "react-dom": "^19.0.0"
+ "react-day-picker": "^8.10.1",
+ "react-dom": "^19.0.0",
+ "react-hook-form": "^7.56.1",
+ "sonner": "^2.0.3",
+ "tailwind-merge": "^3.2.0",
+ "tailwindcss-animate": "^1.0.7",
+ "zod": "^3.24.3"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
@@ -18,6 +38,7 @@
"@types/react": "^19",
"@types/react-dom": "^19",
"tailwindcss": "^4",
+ "tw-animate-css": "^1.2.8",
"typescript": "^5"
}
},
@@ -42,6 +63,51 @@
"tslib": "^2.4.0"
}
},
+ "node_modules/@floating-ui/core": {
+ "version": "1.6.9",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz",
+ "integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==",
+ "dependencies": {
+ "@floating-ui/utils": "^0.2.9"
+ }
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.6.13",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.13.tgz",
+ "integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==",
+ "dependencies": {
+ "@floating-ui/core": "^1.6.0",
+ "@floating-ui/utils": "^0.2.9"
+ }
+ },
+ "node_modules/@floating-ui/react-dom": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz",
+ "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==",
+ "dependencies": {
+ "@floating-ui/dom": "^1.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.9",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz",
+ "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg=="
+ },
+ "node_modules/@hookform/resolvers": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.0.1.tgz",
+ "integrity": "sha512-u/+Jp83luQNx9AdyW2fIPGY6Y7NG68eN2ZW8FOJYL+M0i4s49+refdJdOp/A9n9HFQtQs3HIDHQvX3ZET2o7YA==",
+ "dependencies": {
+ "@standard-schema/utils": "^0.3.0"
+ },
+ "peerDependencies": {
+ "react-hook-form": "^7.55.0"
+ }
+ },
"node_modules/@img/sharp-darwin-arm64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
@@ -509,6 +575,626 @@
"node": ">= 10"
}
},
+ "node_modules/@radix-ui/number": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz",
+ "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="
+ },
+ "node_modules/@radix-ui/primitive": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz",
+ "integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="
+ },
+ "node_modules/@radix-ui/react-arrow": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.4.tgz",
+ "integrity": "sha512-qz+fxrqgNxG0dYew5l7qR3c7wdgRu1XVUHGnGYX7rg5HM4p9SWaRmJwfgR3J0SgyUKayLmzQIun+N6rWRgiRKw==",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-checkbox": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.2.3.tgz",
+ "integrity": "sha512-pHVzDYsnaDmBlAuwim45y3soIN8H4R7KbkSVirGhXO+R/kO2OLCe0eucUEbddaTcdMHHdzcIGHtZSMSQlA+apw==",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.2",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-presence": "1.1.4",
+ "@radix-ui/react-primitive": "2.1.0",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-previous": "1.1.1",
+ "@radix-ui/react-use-size": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-collection": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.4.tgz",
+ "integrity": "sha512-cv4vSf7HttqXilDnAnvINd53OTl1/bjUYVZrkFnA7nwmY9Ob2POUy0WY0sfqBAe1s5FyKsyceQlqiEGPYNTadg==",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.0",
+ "@radix-ui/react-slot": "1.2.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
+ "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-context": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz",
+ "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dialog": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.11.tgz",
+ "integrity": "sha512-yI7S1ipkP5/+99qhSI6nthfo/tR6bL6Zgxi/+1UO6qPa6UeM6nlafWcQ65vB4rU2XjgjMfMhI3k9Y5MztA62VQ==",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.2",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-dismissable-layer": "1.1.7",
+ "@radix-ui/react-focus-guards": "1.1.2",
+ "@radix-ui/react-focus-scope": "1.1.4",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-portal": "1.1.6",
+ "@radix-ui/react-presence": "1.1.4",
+ "@radix-ui/react-primitive": "2.1.0",
+ "@radix-ui/react-slot": "1.2.0",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-direction": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz",
+ "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dismissable-layer": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.7.tgz",
+ "integrity": "sha512-j5+WBUdhccJsmH5/H0K6RncjDtoALSEr6jbkaZu+bjw6hOPOhHycr6vEUujl+HBK8kjUfWcoCJXxP6e4lUlMZw==",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.2",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.0",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-escape-keydown": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-focus-guards": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.2.tgz",
+ "integrity": "sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-focus-scope": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.4.tgz",
+ "integrity": "sha512-r2annK27lIW5w9Ho5NyQgqs0MmgZSTIKXWpVCJaLC1q2kZrZkcqnmHkCHMEmv8XLvsLlurKMPT+kbKkRkm/xVA==",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.0",
+ "@radix-ui/react-use-callback-ref": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-id": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz",
+ "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-label": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.4.tgz",
+ "integrity": "sha512-wy3dqizZnZVV4ja0FNnUhIWNwWdoldXrneEyUcVtLYDAt8ovGS4ridtMAOGgXBBIfggL4BOveVWsjXDORdGEQg==",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popover": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.11.tgz",
+ "integrity": "sha512-yFMfZkVA5G3GJnBgb2PxrrcLKm1ZLWXrbYVgdyTl//0TYEIHS9LJbnyz7WWcZ0qCq7hIlJZpRtxeSeIG5T5oJw==",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.2",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-dismissable-layer": "1.1.7",
+ "@radix-ui/react-focus-guards": "1.1.2",
+ "@radix-ui/react-focus-scope": "1.1.4",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-popper": "1.2.4",
+ "@radix-ui/react-portal": "1.1.6",
+ "@radix-ui/react-presence": "1.1.4",
+ "@radix-ui/react-primitive": "2.1.0",
+ "@radix-ui/react-slot": "1.2.0",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popper": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.4.tgz",
+ "integrity": "sha512-3p2Rgm/a1cK0r/UVkx5F/K9v/EplfjAeIFCGOPYPO4lZ0jtg4iSQXt/YGTSLWaf4x7NG6Z4+uKFcylcTZjeqDA==",
+ "dependencies": {
+ "@floating-ui/react-dom": "^2.0.0",
+ "@radix-ui/react-arrow": "1.1.4",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.0",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-layout-effect": "1.1.1",
+ "@radix-ui/react-use-rect": "1.1.1",
+ "@radix-ui/react-use-size": "1.1.1",
+ "@radix-ui/rect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-portal": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.6.tgz",
+ "integrity": "sha512-XmsIl2z1n/TsYFLIdYam2rmFwf9OC/Sh2avkbmVMDuBZIe7hSpM0cYnWPAo7nHOVx8zTuwDZGByfcqLdnzp3Vw==",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.0",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-presence": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.4.tgz",
+ "integrity": "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.0.tgz",
+ "integrity": "sha512-/J/FhLdK0zVcILOwt5g+dH4KnkonCtkVJsa2G6JmvbbtZfBEI1gMsO3QMjseL4F/SwfAMt1Vc/0XKYKq+xJ1sw==",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.2.tgz",
+ "integrity": "sha512-HjkVHtBkuq+r3zUAZ/CvNWUGKPfuicGDbgtZgiQuFmNcV5F+Tgy24ep2nsAW2nFgvhGPJVqeBZa6KyVN0EyrBA==",
+ "dependencies": {
+ "@radix-ui/number": "1.1.1",
+ "@radix-ui/primitive": "1.1.2",
+ "@radix-ui/react-collection": "1.1.4",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-dismissable-layer": "1.1.7",
+ "@radix-ui/react-focus-guards": "1.1.2",
+ "@radix-ui/react-focus-scope": "1.1.4",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-popper": "1.2.4",
+ "@radix-ui/react-portal": "1.1.6",
+ "@radix-ui/react-primitive": "2.1.0",
+ "@radix-ui/react-slot": "1.2.0",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1",
+ "@radix-ui/react-use-previous": "1.1.1",
+ "@radix-ui/react-visually-hidden": "1.2.0",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-slot": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.0.tgz",
+ "integrity": "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-callback-ref": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
+ "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-controllable-state": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz",
+ "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==",
+ "dependencies": {
+ "@radix-ui/react-use-effect-event": "0.0.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-effect-event": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz",
+ "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-escape-keydown": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz",
+ "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==",
+ "dependencies": {
+ "@radix-ui/react-use-callback-ref": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-layout-effect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
+ "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-previous": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz",
+ "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-rect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz",
+ "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==",
+ "dependencies": {
+ "@radix-ui/rect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-size": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz",
+ "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-visually-hidden": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.0.tgz",
+ "integrity": "sha512-rQj0aAWOpCdCMRbI6pLQm8r7S2BM3YhTa0SzOYD55k+hJA8oo9J+H+9wLM9oMlZWOX/wJWPTzfDfmZkf7LvCfg==",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/rect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz",
+ "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="
+ },
+ "node_modules/@standard-schema/utils": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
+ "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="
+ },
"node_modules/@swc/counter": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
@@ -772,6 +1458,17 @@
"@types/react": "^19.0.0"
}
},
+ "node_modules/aria-hidden": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz",
+ "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/busboy": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
@@ -802,11 +1499,30 @@
}
]
},
+ "node_modules/class-variance-authority": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
+ "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
+ "dependencies": {
+ "clsx": "^2.1.1"
+ },
+ "funding": {
+ "url": "https://polar.sh/cva"
+ }
+ },
"node_modules/client-only": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
},
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/color": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
@@ -854,6 +1570,15 @@
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"dev": true
},
+ "node_modules/date-fns": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
+ "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/kossnocorp"
+ }
+ },
"node_modules/detect-libc": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
@@ -863,6 +1588,11 @@
"node": ">=8"
}
},
+ "node_modules/detect-node-es": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
+ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="
+ },
"node_modules/enhanced-resolve": {
"version": "5.18.1",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
@@ -876,6 +1606,19 @@
"node": ">=10.13.0"
}
},
+ "node_modules/flatpickr": {
+ "version": "4.6.13",
+ "resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.13.tgz",
+ "integrity": "sha512-97PMG/aywoYpB4IvbvUJi0RQi8vearvU0oov1WW3k0WZPBMrTQVqekSX5CjSG/M4Q3i6A/0FKXC7RyAoAUUSPw=="
+ },
+ "node_modules/get-nonce": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
+ "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@@ -1125,6 +1868,14 @@
"url": "https://opencollective.com/parcel"
}
},
+ "node_modules/lucide-react": {
+ "version": "0.503.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.503.0.tgz",
+ "integrity": "sha512-HGGkdlPWQ0vTF8jJ5TdIqhQXZi6uh3LnNgfZ8MHiuxFfX3RZeA79r2MW2tHAZKlAVfoNE8esm3p+O6VkIvpj6w==",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
@@ -1195,6 +1946,20 @@
}
}
},
+ "node_modules/next-crypto": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/next-crypto/-/next-crypto-1.0.8.tgz",
+ "integrity": "sha512-6VcrH+xFuuCRGCdDMjFFibhJ97c4s+J/6SEV73RUYJhh38MDW4WXNZNTWIMZBq0B29LOIfAQ0XA37xGUZZCCjA=="
+ },
+ "node_modules/next-themes": {
+ "version": "0.4.6",
+ "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
+ "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==",
+ "peerDependencies": {
+ "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
+ }
+ },
"node_modules/next/node_modules/postcss": {
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
@@ -1263,6 +2028,19 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-day-picker": {
+ "version": "8.10.1",
+ "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-8.10.1.tgz",
+ "integrity": "sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==",
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/sponsors/gpbl"
+ },
+ "peerDependencies": {
+ "date-fns": "^2.28.0 || ^3.0.0",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
"node_modules/react-dom": {
"version": "19.1.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
@@ -1274,6 +2052,87 @@
"react": "^19.1.0"
}
},
+ "node_modules/react-hook-form": {
+ "version": "7.56.1",
+ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.56.1.tgz",
+ "integrity": "sha512-qWAVokhSpshhcEuQDSANHx3jiAEFzu2HAaaQIzi/r9FNPm1ioAvuJSD4EuZzWd7Al7nTRKcKPnBKO7sRn+zavQ==",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/react-hook-form"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17 || ^18 || ^19"
+ }
+ },
+ "node_modules/react-remove-scroll": {
+ "version": "2.6.3",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.3.tgz",
+ "integrity": "sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ==",
+ "dependencies": {
+ "react-remove-scroll-bar": "^2.3.7",
+ "react-style-singleton": "^2.2.3",
+ "tslib": "^2.1.0",
+ "use-callback-ref": "^1.3.3",
+ "use-sidecar": "^1.1.3"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-remove-scroll-bar": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz",
+ "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==",
+ "dependencies": {
+ "react-style-singleton": "^2.2.2",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-style-singleton": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
+ "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==",
+ "dependencies": {
+ "get-nonce": "^1.0.0",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/scheduler": {
"version": "0.26.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
@@ -1339,6 +2198,15 @@
"is-arrayish": "^0.3.1"
}
},
+ "node_modules/sonner": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.3.tgz",
+ "integrity": "sha512-njQ4Hht92m0sMqqHVDL32V2Oun9W1+PHO9NDv9FHfJjT3JT22IG4Jpo3FPQy+mouRKCXFWO+r67v6MrHX2zeIA==",
+ "peerDependencies": {
+ "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
+ "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ }
+ },
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -1377,12 +2245,29 @@
}
}
},
+ "node_modules/tailwind-merge": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.2.0.tgz",
+ "integrity": "sha512-FQT/OVqCD+7edmmJpsgCsY820RTD5AkBryuG5IUqR5YQZSdj5xlH5nLgH7YPths7WsLPSpSBNneJdM8aS8aeFA==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/dcastil"
+ }
+ },
"node_modules/tailwindcss": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.3.tgz",
"integrity": "sha512-2Q+rw9vy1WFXu5cIxlvsabCwhU2qUwodGq03ODhLJ0jW4ek5BUtoCsnLB0qG+m8AHgEsSJcJGDSDe06FXlP74g==",
"dev": true
},
+ "node_modules/tailwindcss-animate": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz",
+ "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==",
+ "peerDependencies": {
+ "tailwindcss": ">=3.0.0 || insiders"
+ }
+ },
"node_modules/tapable": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
@@ -1397,6 +2282,15 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
},
+ "node_modules/tw-animate-css": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.2.8.tgz",
+ "integrity": "sha512-AxSnYRvyFnAiZCUndS3zQZhNfV/B77ZhJ+O7d3K6wfg/jKJY+yv6ahuyXwnyaYA9UdLqnpCwhTRv9pPTBnPR2g==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/Wombosvideo"
+ }
+ },
"node_modules/typescript": {
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
@@ -1415,6 +2309,55 @@
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
"dev": true
+ },
+ "node_modules/use-callback-ref": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
+ "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-sidecar": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz",
+ "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==",
+ "dependencies": {
+ "detect-node-es": "^1.1.0",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/zod": {
+ "version": "3.24.3",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.3.tgz",
+ "integrity": "sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
}
}
}
diff --git a/WebServices/management-frontend/package.json b/WebServices/management-frontend/package.json
index 8fbe569..2938bbf 100644
--- a/WebServices/management-frontend/package.json
+++ b/WebServices/management-frontend/package.json
@@ -3,22 +3,43 @@
"version": "0.1.0",
"private": true,
"scripts": {
- "dev": "next dev",
+ "dev": "next dev -p 3001",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
+ "@hookform/resolvers": "^5.0.1",
+ "@radix-ui/react-checkbox": "^1.2.3",
+ "@radix-ui/react-dialog": "^1.1.11",
+ "@radix-ui/react-label": "^2.1.4",
+ "@radix-ui/react-popover": "^1.1.11",
+ "@radix-ui/react-select": "^2.2.2",
+ "@radix-ui/react-slot": "^1.2.0",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "date-fns": "^4.1.0",
+ "flatpickr": "^4.6.13",
+ "lucide-react": "^0.503.0",
+ "next": "15.2.4",
+ "next-crypto": "^1.0.8",
+ "next-themes": "^0.4.6",
"react": "^19.0.0",
+ "react-day-picker": "^8.10.1",
"react-dom": "^19.0.0",
- "next": "15.2.4"
+ "react-hook-form": "^7.56.1",
+ "sonner": "^2.0.3",
+ "tailwind-merge": "^3.2.0",
+ "tailwindcss-animate": "^1.0.7",
+ "zod": "^3.24.3"
},
"devDependencies": {
- "typescript": "^5",
+ "@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
- "@tailwindcss/postcss": "^4",
- "tailwindcss": "^4"
+ "tailwindcss": "^4",
+ "tw-animate-css": "^1.2.8",
+ "typescript": "^5"
}
}
diff --git a/WebServices/management-frontend/setup-shadcn.sh b/WebServices/management-frontend/setup-shadcn.sh
new file mode 100755
index 0000000..5f485bd
--- /dev/null
+++ b/WebServices/management-frontend/setup-shadcn.sh
@@ -0,0 +1,45 @@
+#!/bin/bash
+
+# Exit on error
+set -e
+
+echo "🚀 Setting up shadcn/ui components and dependencies..."
+
+# Install required dependencies for shadcn
+echo "📦 Installing required dependencies..."
+npm install tailwindcss-animate class-variance-authority clsx tailwind-merge --legacy-peer-deps
+
+# Initialize shadcn/ui (components.json already exists, so we'll skip this step)
+echo "✅ components.json already exists"
+
+# Set npm config to use legacy-peer-deps for all npm operations
+echo "🔧 Setting npm to use legacy-peer-deps..."
+npm config set legacy-peer-deps true
+
+# Install base components
+echo "🧩 Installing base shadcn/ui components..."
+npx shadcn@latest add button --yes
+npx shadcn@latest add form --yes
+npx shadcn@latest add input --yes
+npx shadcn@latest add label --yes
+npx shadcn@latest add select --yes
+npx shadcn@latest add checkbox --yes
+npx shadcn@latest add card --yes
+npx shadcn@latest add dialog --yes
+npx shadcn@latest add popover --yes
+npx shadcn@latest add sonner --yes
+npx shadcn@latest add table --yes
+npx shadcn@latest add pagination --yes
+npx shadcn@latest add calendar --yes
+npx shadcn@latest add date-picker --yes
+
+# Update any dependencies with legacy peer deps
+echo "🔄 Updating dependencies..."
+npm install --legacy-peer-deps
+npm install next-crypto@^1.0.8 --legacy-peer-deps
+npm install flatpickr@^4.6.13 --legacy-peer-deps
+npm install date-fns@^4.1.0 --legacy-peer-deps
+npm install react-day-picker@^8.10.1 --legacy-peer-deps
+
+echo "✨ Setup complete! You can now use shadcn/ui components in your project."
+echo "📚 Documentation: https://ui.shadcn.com/docs"
diff --git a/WebServices/management-frontend/src/apicalls/api-fetcher.tsx b/WebServices/management-frontend/src/apicalls/api-fetcher.tsx
new file mode 100644
index 0000000..4c6b490
--- /dev/null
+++ b/WebServices/management-frontend/src/apicalls/api-fetcher.tsx
@@ -0,0 +1,144 @@
+"use server";
+import { retrieveAccessToken } from "@/apicalls/cookies/token";
+
+const defaultHeaders = {
+ accept: "application/json",
+ language: "tr",
+ domain: "management.com.tr",
+ tz: "GMT+3",
+ "Content-type": "application/json",
+};
+
+const DefaultResponse = {
+ error: "Hata tipi belirtilmedi",
+ status: "500",
+ data: {},
+};
+
+const cacheList = ["no-cache", "no-store", "force-cache", "only-if-cached"];
+
+const prepareResponse = (response: any, statusCode: number) => {
+ try {
+ return {
+ status: statusCode,
+ data: response || {},
+ };
+ } catch (error) {
+ console.error("Error preparing response:", error);
+ return {
+ ...DefaultResponse,
+ error: "Response parsing error",
+ };
+ }
+};
+
+const fetchData = async (
+ endpoint: string,
+ payload: any,
+ method: string = "POST",
+ cache: boolean = false
+) => {
+ try {
+ const headers = {
+ ...defaultHeaders,
+ };
+ const fetchOptions: RequestInit = {
+ method,
+ headers,
+ cache: cache ? "force-cache" : "no-cache",
+ };
+
+ if (method === "POST" && payload) {
+ fetchOptions.body = JSON.stringify(payload);
+ }
+
+ const response = await fetch(endpoint, fetchOptions);
+ const responseJson = await response.json();
+ console.log("Fetching:", endpoint, fetchOptions);
+ console.log("Response:", responseJson);
+ return prepareResponse(responseJson, response.status);
+ } catch (error) {
+ console.error("Fetch error:", error);
+ return {
+ ...DefaultResponse,
+ error: error instanceof Error ? error.message : "Network error",
+ };
+ }
+};
+
+const updateDataWithToken = async (
+ endpoint: string,
+ uuid: string,
+ payload: any,
+ method: string = "POST",
+ cache: boolean = false
+) => {
+ const accessToken = (await retrieveAccessToken()) || "";
+ const headers = {
+ ...defaultHeaders,
+ "eys-acs-tkn": accessToken,
+ };
+
+ try {
+ const fetchOptions: RequestInit = {
+ method,
+ headers,
+ cache: cache ? "force-cache" : "no-cache",
+ };
+
+ if (method !== "GET" && payload) {
+ fetchOptions.body = JSON.stringify(payload.payload);
+ }
+
+ const response = await fetch(`${endpoint}/${uuid}`, fetchOptions);
+ const responseJson = await response.json();
+ console.log("Fetching:", `${endpoint}/${uuid}`, fetchOptions);
+ console.log("Response:", responseJson);
+ return prepareResponse(responseJson, response.status);
+ } catch (error) {
+ console.error("Update error:", error);
+ return {
+ ...DefaultResponse,
+ error: error instanceof Error ? error.message : "Network error",
+ };
+ }
+};
+
+const fetchDataWithToken = async (
+ endpoint: string,
+ payload: any,
+ method: string = "POST",
+ cache: boolean = false
+) => {
+ const accessToken = (await retrieveAccessToken()) || "";
+ const headers = {
+ ...defaultHeaders,
+ "eys-acs-tkn": accessToken,
+ };
+
+ try {
+ const fetchOptions: RequestInit = {
+ method,
+ headers,
+ cache: cache ? "force-cache" : "no-cache",
+ };
+
+ if (method === "POST" && payload) {
+ fetchOptions.body = JSON.stringify(payload);
+ }
+
+ const response = await fetch(endpoint, fetchOptions);
+ const responseJson = await response.json();
+ console.log("Fetching:", endpoint, fetchOptions);
+ console.log("Response:", responseJson);
+ return prepareResponse(responseJson, response.status);
+ } catch (error) {
+ console.error("Fetch with token error:", error);
+ return {
+ ...DefaultResponse,
+ error: error instanceof Error ? error.message : "Network error",
+ };
+ }
+};
+
+export { fetchData, fetchDataWithToken, updateDataWithToken };
diff --git a/WebServices/management-frontend/src/apicalls/basics.ts b/WebServices/management-frontend/src/apicalls/basics.ts
new file mode 100644
index 0000000..46f4d58
--- /dev/null
+++ b/WebServices/management-frontend/src/apicalls/basics.ts
@@ -0,0 +1,75 @@
+const formatServiceUrl = (url: string) => {
+ if (!url) return "";
+ return url.startsWith("http") ? url : `http://${url}`;
+};
+
+export const baseUrlAuth = formatServiceUrl(
+ process.env.NEXT_PUBLIC_AUTH_SERVICE_URL || "auth_service:8001"
+);
+export const baseUrlPeople = formatServiceUrl(
+ process.env.NEXT_PUBLIC_VALIDATION_SERVICE_URL || "identity_service:8002"
+);
+// export const baseUrlEvent = formatServiceUrl(
+// process.env.NEXT_PUBLIC_EVENT_SERVICE_URL || "eventservice:8888"
+// );
+export const tokenSecret = process.env.TOKENSECRET_90 || "";
+
+export const cookieObject: any = {
+ httpOnly: true,
+ path: "/",
+ sameSite: "none",
+ secure: true,
+ maxAge: 3600,
+ priority: "high",
+};
+
+interface FilterListInterface {
+ page?: number | null | undefined;
+ size?: number | null | undefined;
+ orderField?: string | null | undefined;
+ orderType?: string | null | undefined;
+ includeJoins?: any[] | null | undefined;
+ query?: any | null | undefined;
+}
+
+class FilterList {
+ page: number;
+ size: number;
+ orderField: string;
+ orderType: string;
+ includeJoins: any[];
+ query: any;
+
+ constructor({
+ page = 1,
+ size = 5,
+ orderField = "id",
+ orderType = "asc",
+ includeJoins = [],
+ query = {},
+ }: FilterListInterface = {}) {
+ this.page = page ?? 1;
+ this.size = size ?? 5;
+ this.orderField = orderField ?? "uu_id";
+ this.orderType = orderType ?? "asc";
+ this.orderType = this.orderType.startsWith("a") ? "asc" : "desc";
+ this.includeJoins = includeJoins ?? [];
+ this.query = query ?? {};
+ }
+
+ filter() {
+ return {
+ page: this.page,
+ size: this.size,
+ orderField: this.orderField,
+ orderType: this.orderType,
+ includeJoins: this.includeJoins,
+ query: this.query,
+ };
+ }
+}
+
+const defaultFilterList = new FilterList({});
+
+export { FilterList, defaultFilterList };
+export type { FilterListInterface };
diff --git a/WebServices/management-frontend/src/apicalls/cookies/token.tsx b/WebServices/management-frontend/src/apicalls/cookies/token.tsx
new file mode 100644
index 0000000..194472b
--- /dev/null
+++ b/WebServices/management-frontend/src/apicalls/cookies/token.tsx
@@ -0,0 +1,148 @@
+"use server";
+import { fetchDataWithToken } from "../api-fetcher";
+import { baseUrlAuth, tokenSecret } from "../basics";
+import { cookies } from "next/headers";
+
+import NextCrypto from "next-crypto";
+
+const checkToken = `${baseUrlAuth}/authentication/token/check`;
+const pageValid = `${baseUrlAuth}/authentication/page/valid`;
+const siteUrls = `${baseUrlAuth}/authentication/sites/list`;
+
+const nextCrypto = new NextCrypto(tokenSecret);
+
+async function checkAccessTokenIsValid() {
+ const response = await fetchDataWithToken(checkToken, {}, "GET", false);
+ return response?.status === 200 || response?.status === 202 ? true : false;
+}
+
+async function retrievePageList() {
+ const response = await fetchDataWithToken(siteUrls, {}, "GET", false);
+ return response?.status === 200 || response?.status === 202
+ ? response.data?.sites
+ : null;
+}
+
+async function retrievePagebyUrl(pageUrl: string) {
+ const response = await fetchDataWithToken(
+ pageValid,
+ {
+ page_url: pageUrl,
+ },
+ "POST",
+ false
+ );
+ return response?.status === 200 || response?.status === 202
+ ? response.data?.application
+ : null;
+}
+
+async function retrieveAccessToken() {
+ const cookieStore = await cookies();
+ const encrpytAccessToken = cookieStore.get("accessToken")?.value || "";
+ return encrpytAccessToken
+ ? await nextCrypto.decrypt(encrpytAccessToken)
+ : null;
+}
+
+async function retrieveUserType() {
+ const cookieStore = await cookies();
+ const encrpytaccessObject = cookieStore.get("accessObject")?.value || "{}";
+ const decrpytUserType = JSON.parse(
+ (await nextCrypto.decrypt(encrpytaccessObject)) || "{}"
+ );
+ return decrpytUserType ? decrpytUserType : null;
+}
+
+async function retrieveAccessObjects() {
+ const cookieStore = await cookies();
+ const encrpytAccessObject = cookieStore.get("accessObject")?.value || "";
+ const decrpytAccessObject = await nextCrypto.decrypt(encrpytAccessObject);
+ return decrpytAccessObject ? JSON.parse(decrpytAccessObject) : null;
+}
+
+async function retrieveUserSelection() {
+ const cookieStore = await cookies();
+ const encrpytUserSelection = cookieStore.get("userSelection")?.value || "";
+
+ let objectUserSelection = {};
+ let decrpytUserSelection: any = await nextCrypto.decrypt(
+ encrpytUserSelection
+ );
+ decrpytUserSelection = decrpytUserSelection
+ ? JSON.parse(decrpytUserSelection)
+ : null;
+ console.log("decrpytUserSelection", decrpytUserSelection);
+ const userSelection = decrpytUserSelection?.selected;
+ const accessObjects = (await retrieveAccessObjects()) || {};
+ console.log("accessObjects", accessObjects);
+
+ if (decrpytUserSelection?.user_type === "employee") {
+ const companyList = accessObjects?.selectionList;
+ const selectedCompany = companyList.find(
+ (company: any) => company.uu_id === userSelection
+ );
+ if (selectedCompany) {
+ objectUserSelection = { userType: "employee", selected: selectedCompany };
+ }
+ } else if (decrpytUserSelection?.user_type === "occupant") {
+ const buildingsList = accessObjects?.selectionList;
+
+ // Iterate through all buildings
+ if (buildingsList) {
+ // Loop through each building
+ for (const buildKey in buildingsList) {
+ const building = buildingsList[buildKey];
+
+ // Check if the building has occupants
+ if (building.occupants && building.occupants.length > 0) {
+ // Find the occupant with the matching build_living_space_uu_id
+ const occupant = building.occupants.find(
+ (occ: any) => occ.build_living_space_uu_id === userSelection
+ );
+
+ if (occupant) {
+ objectUserSelection = {
+ userType: "occupant",
+ selected: {
+ ...occupant,
+ buildName: building.build_name,
+ buildNo: building.build_no,
+ },
+ };
+ break;
+ }
+ }
+ }
+ }
+ }
+ return {
+ ...objectUserSelection,
+ };
+}
+
+// const avatarInfo = await retrieveAvatarInfo();
+// lang: avatarInfo?.data?.lang
+// ? String(avatarInfo?.data?.lang).toLowerCase()
+// : undefined,
+// avatar: avatarInfo?.data?.avatar,
+// fullName: avatarInfo?.data?.full_name,
+// async function retrieveAvatarInfo() {
+// const response = await fetchDataWithToken(
+// `${baseUrlAuth}/authentication/avatar`,
+// {},
+// "POST"
+// );
+// return response;
+// }
+
+export {
+ checkAccessTokenIsValid,
+ retrieveAccessToken,
+ retrieveUserType,
+ retrieveAccessObjects,
+ retrieveUserSelection,
+ retrievePagebyUrl,
+ retrievePageList,
+ // retrieveavailablePages,
+};
diff --git a/WebServices/management-frontend/src/apicalls/login/login.tsx b/WebServices/management-frontend/src/apicalls/login/login.tsx
new file mode 100644
index 0000000..42bc0f9
--- /dev/null
+++ b/WebServices/management-frontend/src/apicalls/login/login.tsx
@@ -0,0 +1,183 @@
+"use server";
+import NextCrypto from "next-crypto";
+
+import { fetchData, fetchDataWithToken } from "../api-fetcher";
+import { baseUrlAuth, cookieObject, tokenSecret } from "../basics";
+import { cookies } from "next/headers";
+
+const loginEndpoint = `${baseUrlAuth}/authentication/login`;
+const loginSelectEndpoint = `${baseUrlAuth}/authentication/select`;
+const logoutEndpoint = `${baseUrlAuth}/authentication/logout`;
+
+console.log("loginEndpoint", loginEndpoint);
+console.log("loginSelectEndpoint", loginSelectEndpoint);
+
+interface LoginViaAccessKeys {
+ accessKey: string;
+ password: string;
+ rememberMe: boolean;
+}
+
+interface LoginSelectEmployee {
+ company_uu_id: string;
+}
+
+interface LoginSelectOccupant {
+ build_living_space_uu_id: any;
+}
+
+async function logoutActiveSession() {
+ const cookieStore = await cookies();
+ const response = await fetchDataWithToken(logoutEndpoint, {}, "GET", false);
+ cookieStore.delete("accessToken");
+ cookieStore.delete("accessObject");
+ cookieStore.delete("userProfile");
+ cookieStore.delete("userSelection");
+ return response;
+}
+
+async function loginViaAccessKeys(payload: LoginViaAccessKeys) {
+ try {
+ const cookieStore = await cookies();
+ const nextCrypto = new NextCrypto(tokenSecret);
+
+ const response = await fetchData(
+ loginEndpoint,
+ {
+ access_key: payload.accessKey,
+ password: payload.password,
+ remember_me: payload.rememberMe,
+ },
+ "POST",
+ false
+ );
+ console.log("response", response);
+ if (response.status === 200 || response.status === 202) {
+ const loginRespone = response?.data;
+ const accessToken = await nextCrypto.encrypt(loginRespone.access_token);
+ const accessObject = await nextCrypto.encrypt(
+ JSON.stringify({
+ userType: loginRespone.user_type,
+ selectionList: loginRespone.selection_list,
+ })
+ );
+ const userProfile = await nextCrypto.encrypt(
+ JSON.stringify(loginRespone.user)
+ );
+
+ cookieStore.set({
+ name: "accessToken",
+ value: accessToken,
+ ...cookieObject,
+ });
+ cookieStore.set({
+ name: "accessObject",
+ value: accessObject,
+ ...cookieObject,
+ });
+ cookieStore.set({
+ name: "userProfile",
+ value: JSON.stringify(userProfile),
+ ...cookieObject,
+ });
+ try {
+ return {
+ completed: true,
+ message: "Login successful",
+ status: 200,
+ data: loginRespone,
+ };
+ } catch (error) {
+ console.error("JSON parse error:", error);
+ return {
+ completed: false,
+ message: "Login NOT successful",
+ status: 401,
+ data: "{}",
+ };
+ }
+ }
+
+ return {
+ completed: false,
+ // error: response.error || "Login failed",
+ // message: response.message || "Authentication failed",
+ status: response.status || 500,
+ };
+ } catch (error) {
+ console.error("Login error:", error);
+ return {
+ completed: false,
+ // error: error instanceof Error ? error.message : "Login error",
+ // message: "An error occurred during login",
+ status: 500,
+ };
+ }
+}
+
+async function loginSelectEmployee(payload: LoginSelectEmployee) {
+ const cookieStore = await cookies();
+ const nextCrypto = new NextCrypto(tokenSecret);
+ const companyUUID = payload.company_uu_id;
+ const selectResponse: any = await fetchDataWithToken(
+ loginSelectEndpoint,
+ {
+ company_uu_id: companyUUID,
+ },
+ "POST",
+ false
+ );
+ cookieStore.delete("userSelection");
+
+ if (selectResponse.status === 200 || selectResponse.status === 202) {
+ const usersSelection = await nextCrypto.encrypt(
+ JSON.stringify({
+ selected: companyUUID,
+ user_type: "employee",
+ })
+ );
+ cookieStore.set({
+ name: "userSelection",
+ value: usersSelection,
+ ...cookieObject,
+ });
+ }
+ return selectResponse;
+}
+
+async function loginSelectOccupant(payload: LoginSelectOccupant) {
+ const livingSpaceUUID = payload.build_living_space_uu_id;
+ const cookieStore = await cookies();
+ const nextCrypto = new NextCrypto(tokenSecret);
+ const selectResponse: any = await fetchDataWithToken(
+ loginSelectEndpoint,
+ {
+ build_living_space_uu_id: livingSpaceUUID,
+ },
+ "POST",
+ false
+ );
+ cookieStore.delete("userSelection");
+
+ if (selectResponse.status === 200 || selectResponse.status === 202) {
+ const usersSelection = await nextCrypto.encrypt(
+ JSON.stringify({
+ selected: livingSpaceUUID,
+ user_type: "occupant",
+ })
+ );
+ cookieStore.set({
+ name: "userSelection",
+ value: usersSelection,
+ ...cookieObject,
+ });
+ }
+ return selectResponse;
+}
+
+export {
+ loginViaAccessKeys,
+ loginSelectEmployee,
+ loginSelectOccupant,
+ logoutActiveSession,
+};
diff --git a/WebServices/management-frontend/src/apicalls/schemas/list.tsx b/WebServices/management-frontend/src/apicalls/schemas/list.tsx
new file mode 100644
index 0000000..659fb37
--- /dev/null
+++ b/WebServices/management-frontend/src/apicalls/schemas/list.tsx
@@ -0,0 +1,14 @@
+export interface PaginateOnly {
+ page?: number;
+ size?: number;
+ orderField?: string[];
+ orderType?: string[];
+}
+
+export interface PageListOptions {
+ page?: number;
+ size?: number;
+ orderField?: string[];
+ orderType?: string[];
+ query?: any;
+}
diff --git a/WebServices/management-frontend/src/app/(AuthLayout)/auth/login/page.tsx b/WebServices/management-frontend/src/app/(AuthLayout)/auth/login/page.tsx
new file mode 100644
index 0000000..2d20e55
--- /dev/null
+++ b/WebServices/management-frontend/src/app/(AuthLayout)/auth/login/page.tsx
@@ -0,0 +1,16 @@
+import React from "react";
+import Login from "@/components/auth/login";
+import { Metadata } from "next";
+
+export const metadata: Metadata = {
+ title: "WAG Login",
+ description: "Login to WAG system",
+};
+
+export default function LoginPage() {
+ return (
+ <>
+
+ >
+ );
+}
diff --git a/WebServices/management-frontend/src/app/(AuthLayout)/auth/select/page.tsx b/WebServices/management-frontend/src/app/(AuthLayout)/auth/select/page.tsx
new file mode 100644
index 0000000..d079ba3
--- /dev/null
+++ b/WebServices/management-frontend/src/app/(AuthLayout)/auth/select/page.tsx
@@ -0,0 +1,42 @@
+"use server";
+import React from "react";
+import {
+ checkAccessTokenIsValid,
+ retrieveUserType,
+} from "@/apicalls/cookies/token";
+import { redirect } from "next/navigation";
+import LoginEmployee from "@/components/auth/LoginEmployee";
+import LoginOccupant from "@/components/auth/LoginOccupant";
+
+async function SelectPage() {
+ const token_is_valid = await checkAccessTokenIsValid();
+ const selection = await retrieveUserType();
+ console.log("selection", selection);
+
+ const isEmployee = selection?.userType == "employee";
+ const isOccupant = selection?.userType == "occupant";
+
+ const selectionList = selection?.selectionList;
+
+ if (!selectionList || !token_is_valid) {
+ redirect("/auth/login");
+ }
+
+ return (
+ <>
+
+
+ {isEmployee && Array.isArray(selectionList) && (
+
+ )}
+
+ {isOccupant && !Array.isArray(selectionList) && (
+
+ )}
+
+
+ >
+ );
+}
+
+export default SelectPage;
diff --git a/WebServices/management-frontend/src/app/(AuthLayout)/layout.tsx b/WebServices/management-frontend/src/app/(AuthLayout)/layout.tsx
new file mode 100644
index 0000000..2353a89
--- /dev/null
+++ b/WebServices/management-frontend/src/app/(AuthLayout)/layout.tsx
@@ -0,0 +1,29 @@
+import type { Metadata } from "next";
+import { Suspense } from "react";
+
+export const metadata: Metadata = {
+ title: "WAG Frontend",
+ description: "WAG Frontend Application",
+};
+
+export default function AuthLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return (
+
+
+
+
WAG Frontend
+
+ Welcome to the WAG Frontend Application
+
+
+
+
+ Loading...
}>{children}
+
+
+ );
+}
diff --git a/WebServices/management-frontend/src/app/(DashboardLayout)/dashboard/page.tsx b/WebServices/management-frontend/src/app/(DashboardLayout)/dashboard/page.tsx
new file mode 100644
index 0000000..d4bc63f
--- /dev/null
+++ b/WebServices/management-frontend/src/app/(DashboardLayout)/dashboard/page.tsx
@@ -0,0 +1,35 @@
+import React from "react";
+import Header from "@/components/header/Header";
+import ClientMenu from "@/components/menu/menu";
+import { retrievePageByUrl } from "@/components/Pages/pageRetriever";
+
+async function PageDashboard({
+ searchParams,
+}: {
+ searchParams: Promise<{ [key: string]: string | undefined }>;
+}) {
+ const activePage = "/dashboard";
+ const searchParamsInstance = await searchParams;
+ const lang = (searchParamsInstance?.lang as "en" | "tr") || "en";
+ const PageComponent = retrievePageByUrl(activePage);
+
+ return (
+ <>
+