diff --git a/.gitignore b/.gitignore
index c887eb2..e92cf10 100644
--- a/.gitignore
+++ b/.gitignore
@@ -128,6 +128,10 @@ dmypy.json
*~
.DS_Store
+# Node.js / Frontend
+node_modules/
+.vite/
+
# Swift Package Manager (macOS app)
EXStreamTVApp/.build/
diff --git a/exstreamtv/database/models/template.py b/exstreamtv/database/models/template.py
index de97fe1..c45af0f 100644
--- a/exstreamtv/database/models/template.py
+++ b/exstreamtv/database/models/template.py
@@ -2,7 +2,7 @@
Template Database Models
Defines Template, TemplateGroup, and TemplateItem for scheduling templates.
-ErsatzTV feature: reusable schedule patterns.
+Reusable schedule patterns for programming automation.
"""
from datetime import time
@@ -42,7 +42,7 @@ class Template(Base, TimestampMixin):
"""
Schedule template for reusable programming patterns.
- ErsatzTV feature: define a day's programming once, apply to multiple days.
+ Define a day's programming once, then apply to multiple days.
"""
__tablename__ = "templates"
diff --git a/exstreamtv/main.py b/exstreamtv/main.py
index 0a8fe27..2ca59d9 100644
--- a/exstreamtv/main.py
+++ b/exstreamtv/main.py
@@ -1,7 +1,7 @@
"""
EXStreamTV Main Application
-FastAPI application entry point combining StreamTV and ErsatzTV features.
+FastAPI application entry point — unified IPTV streaming platform.
"""
import asyncio
@@ -406,7 +406,7 @@ def create_app() -> FastAPI:
"""
app = FastAPI(
title="EXStreamTV",
- description="Unified IPTV streaming platform combining StreamTV and ErsatzTV",
+ description="Unified IPTV streaming platform",
version=__version__,
lifespan=lifespan,
docs_url="/api/docs",
@@ -522,7 +522,7 @@ async def root_lineup():
except ImportError as e:
logger.warning(f"HDHomeRun router not available: {e}")
- # Migration router for ErsatzTV/StreamTV imports
+ # Migration router for legacy imports
try:
from exstreamtv.api import migration_api
app.include_router(migration_api.router, prefix="/api/migration", tags=["Migration"])
@@ -997,7 +997,7 @@ async def test_stream_page(request: Request):
Features
- ErsatzTV-style continuous background streaming
+ Continuous background streaming with anchor-time playout
HDHomeRun emulation for Plex/Emby/Jellyfin
M3U playlist support with XMLTV EPG
FFmpeg transcoding with hardware acceleration
@@ -1271,10 +1271,10 @@ async def import_m3u_page(request: Request):
)
return RedirectResponse(url="/api/docs")
- # Migration page - Import from ErsatzTV/StreamTV
+ # Migration page - Import from legacy systems
@app.get("/migration", response_model=None)
async def migration_page(request: Request):
- """Migration page for importing from ErsatzTV and StreamTV."""
+ """Migration page for importing from legacy systems."""
if templates:
return templates.TemplateResponse(
"migration.html",
diff --git a/exstreamtv/playout/scheduler.py b/exstreamtv/playout/scheduler.py
index 0cfd191..1a0e5cc 100644
--- a/exstreamtv/playout/scheduler.py
+++ b/exstreamtv/playout/scheduler.py
@@ -1,7 +1,7 @@
"""
Playout scheduler for determining what plays when.
-Ported from ErsatzTV PlayoutModeScheduler*.cs files.
+Supports four scheduling modes: ONE, MULTIPLE, DURATION, and FLOOD.
"""
from abc import ABC, abstractmethod
@@ -22,7 +22,8 @@ class ScheduleMode(Enum):
"""
Schedule item playback mode.
- Ported from ErsatzTV PlayoutMode.
+ ONE: play one item then advance. MULTIPLE: play N items. DURATION: play
+ for a time span. FLOOD: play until a fixed end time.
"""
ONE = "one" # Play one item, then move to next schedule item
@@ -35,8 +36,6 @@ class ScheduleMode(Enum):
class ScheduleItem:
"""
A schedule item defining what to play and when.
-
- Ported from ErsatzTV ProgramScheduleItem.
"""
id: int
@@ -84,7 +83,8 @@ class PlayoutScheduler:
"""
Main scheduler that determines playout items.
- Ported from ErsatzTV PlayoutModeScheduler* classes.
+ Processes schedule items in sequence, delegating to mode-specific logic
+ (ONE, MULTIPLE, DURATION, FLOOD) to fill a playout timeline.
"""
def __init__(
diff --git a/exstreamtv/streaming/channel_manager.py b/exstreamtv/streaming/channel_manager.py
index 41d862f..29ed617 100644
--- a/exstreamtv/streaming/channel_manager.py
+++ b/exstreamtv/streaming/channel_manager.py
@@ -1,9 +1,9 @@
"""
-Channel Manager for continuous background streaming (ErsatzTV-style).
+Channel Manager for continuous background streaming.
-Ported from StreamTV with playout timeline tracking and resume capability.
+Provides playout timeline tracking and resume capability.
-Enhanced with Tunarr/dizqueTV integrations:
+Enhanced with integrations:
- Session tracking for connection management
- Stream throttling for buffer control
- Error screen fallback for graceful failures
@@ -107,7 +107,7 @@ def _safe_import(module_path: str, name: str) -> Optional[Any]:
class ChannelStream:
"""
- Manages a continuous stream for a single channel (ErsatzTV-style).
+ Manages a continuous stream for a single channel.
Features:
- Continuous background streaming
@@ -153,7 +153,7 @@ def __init__(
self._lock = asyncio.Lock()
self._client_count = 0
- # Playout timeline tracking (ErsatzTV-style)
+ # Playout timeline tracking
self._playout_start_time: datetime | None = None
self._schedule_items: list[dict] = []
self._current_item_index = 0
@@ -226,7 +226,7 @@ async def _load_or_initialize_position(self) -> None:
"""
Load saved anchor time or initialize for continuous streaming.
- ErsatzTV-style approach: Use an anchor time to calculate the current
+ Uses an anchor time to calculate the current
position based on elapsed wall-clock time. This ensures:
1. All viewers see the same content at the same time
2. Restarting the server continues from the correct time-based position
@@ -460,7 +460,7 @@ async def get_stream(self) -> AsyncIterator[bytes]:
"""
Get the current stream.
- Joins existing continuous stream at current position (ErsatzTV-style).
+ Joins existing continuous stream at current position.
Yields:
MPEG-TS data chunks.
@@ -763,7 +763,7 @@ async def _stream_loop(
f"Channel {self.channel_number} playing: {playout_item.get('title')}"
)
- # Get seek offset from playout item (ErsatzTV-style)
+ # Get seek offset from playout item
seek_offset = playout_item.get("seek_offset", 0.0)
# Update current item tracking (EPG uses this for guide alignment)
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
new file mode 100644
index 0000000..4ac794f
--- /dev/null
+++ b/frontend/package-lock.json
@@ -0,0 +1,2685 @@
+{
+ "name": "exstreamtv-frontend",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "exstreamtv-frontend",
+ "version": "0.1.0",
+ "dependencies": {
+ "@emotion/react": "^11.14.0",
+ "@emotion/styled": "^11.14.1",
+ "@hookform/resolvers": "^5.2.2",
+ "@mui/icons-material": "^7.3.9",
+ "@mui/material": "^7.3.9",
+ "@tanstack/react-query": "^5.96.2",
+ "notistack": "^3.0.2",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "react-hook-form": "^7.72.1",
+ "react-router-dom": "^6.28.0",
+ "zod": "^4.3.6",
+ "zustand": "^5.0.12"
+ },
+ "devDependencies": {
+ "@types/react": "^18.3.12",
+ "@types/react-dom": "^18.3.1",
+ "@vitejs/plugin-react": "^4.3.4",
+ "typescript": "~5.6.2",
+ "vite": "^5.4.11"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
+ "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
+ "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
+ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-module-transforms": "^7.28.6",
+ "@babel/helpers": "^7.28.6",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/traverse": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/core/node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.29.1",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
+ "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
+ "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.28.6",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
+ "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
+ "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/traverse": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
+ "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.29.2",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz",
+ "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.29.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.2",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz",
+ "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
+ "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
+ "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.29.2",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
+ "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
+ "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.29.0",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+ "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@emotion/babel-plugin": {
+ "version": "11.13.5",
+ "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
+ "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.16.7",
+ "@babel/runtime": "^7.18.3",
+ "@emotion/hash": "^0.9.2",
+ "@emotion/memoize": "^0.9.0",
+ "@emotion/serialize": "^1.3.3",
+ "babel-plugin-macros": "^3.1.0",
+ "convert-source-map": "^1.5.0",
+ "escape-string-regexp": "^4.0.0",
+ "find-root": "^1.1.0",
+ "source-map": "^0.5.7",
+ "stylis": "4.2.0"
+ }
+ },
+ "node_modules/@emotion/cache": {
+ "version": "11.14.0",
+ "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz",
+ "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==",
+ "license": "MIT",
+ "dependencies": {
+ "@emotion/memoize": "^0.9.0",
+ "@emotion/sheet": "^1.4.0",
+ "@emotion/utils": "^1.4.2",
+ "@emotion/weak-memoize": "^0.4.0",
+ "stylis": "4.2.0"
+ }
+ },
+ "node_modules/@emotion/hash": {
+ "version": "0.9.2",
+ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz",
+ "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/is-prop-valid": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz",
+ "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==",
+ "license": "MIT",
+ "dependencies": {
+ "@emotion/memoize": "^0.9.0"
+ }
+ },
+ "node_modules/@emotion/memoize": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
+ "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/react": {
+ "version": "11.14.0",
+ "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
+ "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.18.3",
+ "@emotion/babel-plugin": "^11.13.5",
+ "@emotion/cache": "^11.14.0",
+ "@emotion/serialize": "^1.3.3",
+ "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0",
+ "@emotion/utils": "^1.4.2",
+ "@emotion/weak-memoize": "^0.4.0",
+ "hoist-non-react-statics": "^3.3.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@emotion/serialize": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz",
+ "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==",
+ "license": "MIT",
+ "dependencies": {
+ "@emotion/hash": "^0.9.2",
+ "@emotion/memoize": "^0.9.0",
+ "@emotion/unitless": "^0.10.0",
+ "@emotion/utils": "^1.4.2",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@emotion/sheet": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz",
+ "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/styled": {
+ "version": "11.14.1",
+ "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz",
+ "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.18.3",
+ "@emotion/babel-plugin": "^11.13.5",
+ "@emotion/is-prop-valid": "^1.3.0",
+ "@emotion/serialize": "^1.3.3",
+ "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0",
+ "@emotion/utils": "^1.4.2"
+ },
+ "peerDependencies": {
+ "@emotion/react": "^11.0.0-rc.0",
+ "react": ">=16.8.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@emotion/unitless": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz",
+ "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/use-insertion-effect-with-fallbacks": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz",
+ "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@emotion/utils": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz",
+ "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/weak-memoize": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz",
+ "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==",
+ "license": "MIT"
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@hookform/resolvers": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.2.2.tgz",
+ "integrity": "sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==",
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/utils": "^0.3.0"
+ },
+ "peerDependencies": {
+ "react-hook-form": "^7.55.0"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@mui/core-downloads-tracker": {
+ "version": "7.3.9",
+ "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.9.tgz",
+ "integrity": "sha512-MOkOCTfbMJwLshlBCKJ59V2F/uaLYfmKnN76kksj6jlGUVdI25A9Hzs08m+zjBRdLv+sK7Rqdsefe8X7h/6PCw==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ }
+ },
+ "node_modules/@mui/icons-material": {
+ "version": "7.3.9",
+ "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.3.9.tgz",
+ "integrity": "sha512-BT+zPJXss8Hg/oEMRmHl17Q97bPACG4ufFSfGEdhiE96jOyR5Dz1ty7ZWt1fVGR0y1p+sSgEwQT/MNZQmoWDCw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@mui/material": "^7.3.9",
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/material": {
+ "version": "7.3.9",
+ "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.9.tgz",
+ "integrity": "sha512-I8yO3t4T0y7bvDiR1qhIN6iBWZOTBfVOnmLlM7K6h3dx5YX2a7rnkuXzc2UkZaqhxY9NgTnEbdPlokR1RxCNRQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.28.6",
+ "@mui/core-downloads-tracker": "^7.3.9",
+ "@mui/system": "^7.3.9",
+ "@mui/types": "^7.4.12",
+ "@mui/utils": "^7.3.9",
+ "@popperjs/core": "^2.11.8",
+ "@types/react-transition-group": "^4.4.12",
+ "clsx": "^2.1.1",
+ "csstype": "^3.2.3",
+ "prop-types": "^15.8.1",
+ "react-is": "^19.2.3",
+ "react-transition-group": "^4.4.5"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@emotion/react": "^11.5.0",
+ "@emotion/styled": "^11.3.0",
+ "@mui/material-pigment-css": "^7.3.9",
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/react": {
+ "optional": true
+ },
+ "@emotion/styled": {
+ "optional": true
+ },
+ "@mui/material-pigment-css": {
+ "optional": true
+ },
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/private-theming": {
+ "version": "7.3.9",
+ "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.9.tgz",
+ "integrity": "sha512-ErIyRQvsiQEq7Yvcvfw9UDHngaqjMy9P3JDPnRAaKG5qhpl2C4tX/W1S4zJvpu+feihmZJStjIyvnv6KDbIrlw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.28.6",
+ "@mui/utils": "^7.3.9",
+ "prop-types": "^15.8.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/styled-engine": {
+ "version": "7.3.9",
+ "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.3.9.tgz",
+ "integrity": "sha512-JqujWt5bX4okjUPGpVof/7pvgClqh7HvIbsIBIOOlCh2u3wG/Bwp4+E1bc1dXSwkrkp9WUAoNdI5HEC+5HKvMw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.28.6",
+ "@emotion/cache": "^11.14.0",
+ "@emotion/serialize": "^1.3.3",
+ "@emotion/sheet": "^1.4.0",
+ "csstype": "^3.2.3",
+ "prop-types": "^15.8.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@emotion/react": "^11.4.1",
+ "@emotion/styled": "^11.3.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/react": {
+ "optional": true
+ },
+ "@emotion/styled": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/system": {
+ "version": "7.3.9",
+ "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.9.tgz",
+ "integrity": "sha512-aL1q9am8XpRrSabv9qWf5RHhJICJql34wnrc1nz0MuOglPRYF/liN+c8VqZdTvUn9qg+ZjRVbKf4sJVFfIDtmg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.28.6",
+ "@mui/private-theming": "^7.3.9",
+ "@mui/styled-engine": "^7.3.9",
+ "@mui/types": "^7.4.12",
+ "@mui/utils": "^7.3.9",
+ "clsx": "^2.1.1",
+ "csstype": "^3.2.3",
+ "prop-types": "^15.8.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@emotion/react": "^11.5.0",
+ "@emotion/styled": "^11.3.0",
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/react": {
+ "optional": true
+ },
+ "@emotion/styled": {
+ "optional": true
+ },
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/types": {
+ "version": "7.4.12",
+ "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.12.tgz",
+ "integrity": "sha512-iKNAF2u9PzSIj40CjvKJWxFXJo122jXVdrmdh0hMYd+FR+NuJMkr/L88XwWLCRiJ5P1j+uyac25+Kp6YC4hu6w==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.28.6"
+ },
+ "peerDependencies": {
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/utils": {
+ "version": "7.3.9",
+ "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.9.tgz",
+ "integrity": "sha512-U6SdZaGbfb65fqTsH3V5oJdFj9uYwyLE2WVuNvmbggTSDBb8QHrFsqY8BN3taK9t3yJ8/BPHD/kNvLNyjwM7Yw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.28.6",
+ "@mui/types": "^7.4.12",
+ "@types/prop-types": "^15.7.15",
+ "clsx": "^2.1.1",
+ "prop-types": "^15.8.1",
+ "react-is": "^19.2.3"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@popperjs/core": {
+ "version": "2.11.8",
+ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
+ "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/popperjs"
+ }
+ },
+ "node_modules/@remix-run/router": {
+ "version": "1.23.2",
+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz",
+ "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.27",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz",
+ "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz",
+ "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz",
+ "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz",
+ "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz",
+ "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz",
+ "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz",
+ "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz",
+ "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz",
+ "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz",
+ "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz",
+ "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz",
+ "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz",
+ "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz",
+ "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz",
+ "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz",
+ "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz",
+ "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz",
+ "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz",
+ "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz",
+ "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz",
+ "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz",
+ "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz",
+ "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz",
+ "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz",
+ "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "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==",
+ "license": "MIT"
+ },
+ "node_modules/@tanstack/query-core": {
+ "version": "5.96.2",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.96.2.tgz",
+ "integrity": "sha512-hzI6cTVh4KNRk8UtoIBS7Lv9g6BnJPXvBKsvYH1aGWvv0347jT3BnSvztOE+kD76XGvZnRC/t6qdW1CaIfwCeA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/react-query": {
+ "version": "5.96.2",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.96.2.tgz",
+ "integrity": "sha512-sYyzzJT4G0g02azzJ8o55VFFV31XvFpdUpG+unxS0vSaYsJnSPKGoI6WdPwUucJL1wpgGfwfmntNX/Ub1uOViA==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/query-core": "5.96.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19"
+ }
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/parse-json": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
+ "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.15",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
+ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "18.3.28",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz",
+ "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/prop-types": "*",
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "18.3.7",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
+ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^18.0.0"
+ }
+ },
+ "node_modules/@types/react-transition-group": {
+ "version": "4.4.12",
+ "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
+ "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.28.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-beta.27",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.17.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/babel-plugin-macros": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
+ "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "cosmiconfig": "^7.0.0",
+ "resolve": "^1.19.0"
+ },
+ "engines": {
+ "node": ">=10",
+ "npm": ">=6"
+ }
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.10.15",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.15.tgz",
+ "integrity": "sha512-1nfKCq9wuAZFTkA2ey/3OXXx7GzFjLdkTiFVNwlJ9WqdI706CZRIhEqjuwanjMIja+84jDLa9rcyZDPDiVkASQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.2",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
+ "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.10.12",
+ "caniuse-lite": "^1.0.30001782",
+ "electron-to-chromium": "^1.5.328",
+ "node-releases": "^2.0.36",
+ "update-browserslist-db": "^1.2.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001785",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001785.tgz",
+ "integrity": "sha512-blhOL/WNR+Km1RI/LCVAvA73xplXA7ZbjzI4YkMK9pa6T/P3F2GxjNpEkyw5repTw9IvkyrjyHpwjnhZ5FOvYQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/convert-source-map": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
+ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
+ "license": "MIT"
+ },
+ "node_modules/cosmiconfig": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
+ "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/parse-json": "^4.0.0",
+ "import-fresh": "^3.2.1",
+ "parse-json": "^5.0.0",
+ "path-type": "^4.0.0",
+ "yaml": "^1.10.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/dom-helpers": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
+ "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.8.7",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.331",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.331.tgz",
+ "integrity": "sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
+ "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/find-root": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
+ "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==",
+ "license": "MIT"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/goober": {
+ "version": "2.1.18",
+ "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.18.tgz",
+ "integrity": "sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "csstype": "^3.0.10"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/hoist-non-react-statics": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+ "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "react-is": "^16.7.0"
+ }
+ },
+ "node_modules/hoist-non-react-statics/node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
+ "license": "MIT"
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "license": "MIT"
+ },
+ "node_modules/is-core-module": {
+ "version": "2.16.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "license": "MIT"
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "license": "MIT"
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.37",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz",
+ "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/notistack": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/notistack/-/notistack-3.0.2.tgz",
+ "integrity": "sha512-0R+/arLYbK5Hh7mEfR2adt0tyXJcCC9KkA2hc56FeWik2QN6Bm/S4uW+BjzDARsJth5u06nTjelSw/VSnB1YEA==",
+ "license": "MIT",
+ "dependencies": {
+ "clsx": "^1.1.0",
+ "goober": "^2.0.33"
+ },
+ "engines": {
+ "node": ">=12.0.0",
+ "npm": ">=6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/notistack"
+ },
+ "peerDependencies": {
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/notistack/node_modules/clsx": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
+ "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "license": "MIT"
+ },
+ "node_modules/path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC"
+ },
+ "node_modules/postcss": {
+ "version": "8.5.8",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
+ "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/prop-types": {
+ "version": "15.8.1",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+ "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.13.1"
+ }
+ },
+ "node_modules/prop-types/node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
+ "license": "MIT"
+ },
+ "node_modules/react": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/react-hook-form": {
+ "version": "7.72.1",
+ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.72.1.tgz",
+ "integrity": "sha512-RhwBoy2ygeVZje+C+bwJ8g0NjTdBmDlJvAUHTxRjTmSUKPYsKfMphkS2sgEMotsY03bP358yEYlnUeZy//D9Ig==",
+ "license": "MIT",
+ "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-is": {
+ "version": "19.2.4",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.4.tgz",
+ "integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==",
+ "license": "MIT"
+ },
+ "node_modules/react-refresh": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-router": {
+ "version": "6.30.3",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz",
+ "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==",
+ "license": "MIT",
+ "dependencies": {
+ "@remix-run/router": "1.23.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8"
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "6.30.3",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz",
+ "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==",
+ "license": "MIT",
+ "dependencies": {
+ "@remix-run/router": "1.23.2",
+ "react-router": "6.30.3"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8",
+ "react-dom": ">=16.8"
+ }
+ },
+ "node_modules/react-transition-group": {
+ "version": "4.4.5",
+ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
+ "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@babel/runtime": "^7.5.5",
+ "dom-helpers": "^5.0.1",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.6.2"
+ },
+ "peerDependencies": {
+ "react": ">=16.6.0",
+ "react-dom": ">=16.6.0"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.11",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
+ "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-core-module": "^2.16.1",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz",
+ "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.60.1",
+ "@rollup/rollup-android-arm64": "4.60.1",
+ "@rollup/rollup-darwin-arm64": "4.60.1",
+ "@rollup/rollup-darwin-x64": "4.60.1",
+ "@rollup/rollup-freebsd-arm64": "4.60.1",
+ "@rollup/rollup-freebsd-x64": "4.60.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.60.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.60.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.60.1",
+ "@rollup/rollup-linux-arm64-musl": "4.60.1",
+ "@rollup/rollup-linux-loong64-gnu": "4.60.1",
+ "@rollup/rollup-linux-loong64-musl": "4.60.1",
+ "@rollup/rollup-linux-ppc64-gnu": "4.60.1",
+ "@rollup/rollup-linux-ppc64-musl": "4.60.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.60.1",
+ "@rollup/rollup-linux-riscv64-musl": "4.60.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.60.1",
+ "@rollup/rollup-linux-x64-gnu": "4.60.1",
+ "@rollup/rollup-linux-x64-musl": "4.60.1",
+ "@rollup/rollup-openbsd-x64": "4.60.1",
+ "@rollup/rollup-openharmony-arm64": "4.60.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.60.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.60.1",
+ "@rollup/rollup-win32-x64-gnu": "4.60.1",
+ "@rollup/rollup-win32-x64-msvc": "4.60.1",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/stylis": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
+ "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==",
+ "license": "MIT"
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.6.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
+ "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "5.4.21",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
+ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yaml": {
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz",
+ "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/zod": {
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
+ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zustand": {
+ "version": "5.0.12",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.12.tgz",
+ "integrity": "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=18.0.0",
+ "immer": ">=9.0.6",
+ "react": ">=18.0.0",
+ "use-sync-external-store": ">=1.2.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "use-sync-external-store": {
+ "optional": true
+ }
+ }
+ }
+ }
+}
diff --git a/frontend/package.json b/frontend/package.json
index 6ca1814..cf06a23 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -9,17 +9,24 @@
"preview": "vite preview"
},
"dependencies": {
+ "@emotion/react": "^11.14.0",
+ "@emotion/styled": "^11.14.1",
+ "@hookform/resolvers": "^5.2.2",
+ "@mui/icons-material": "^7.3.9",
+ "@mui/material": "^7.3.9",
+ "@tanstack/react-query": "^5.96.2",
+ "notistack": "^3.0.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
- "react-router-dom": "^6.28.0"
+ "react-hook-form": "^7.72.1",
+ "react-router-dom": "^6.28.0",
+ "zod": "^4.3.6",
+ "zustand": "^5.0.12"
},
"devDependencies": {
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.4",
- "autoprefixer": "^10.4.20",
- "postcss": "^8.4.49",
- "tailwindcss": "^3.4.15",
"typescript": "~5.6.2",
"vite": "^5.4.11"
}
diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js
deleted file mode 100644
index 2aa7205..0000000
--- a/frontend/postcss.config.js
+++ /dev/null
@@ -1,6 +0,0 @@
-export default {
- plugins: {
- tailwindcss: {},
- autoprefixer: {},
- },
-};
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index fb7086d..f30de31 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -1,13 +1,24 @@
import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom";
import { PersonaProvider } from "./context/PersonaContext";
import AppShell from "./layout/AppShell";
-import ChannelDetailPage from "./pages/ChannelDetailPage";
-import ChannelsPage from "./pages/ChannelsPage";
import Dashboard from "./pages/Dashboard";
-import ScheduleHistoryPage from "./pages/ScheduleHistoryPage";
-import ScheduleDetailPage from "./pages/ScheduleDetailPage";
+import ChannelsPage from "./pages/ChannelsPage";
+import ChannelDetailPage from "./pages/ChannelDetailPage";
import SchedulesPage from "./pages/SchedulesPage";
-import SettingsPage from "./pages/SettingsPage";
+import ScheduleDetailPage from "./pages/ScheduleDetailPage";
+import ScheduleHistoryPage from "./pages/ScheduleHistoryPage";
+import GuidePage from "./pages/GuidePage";
+import LibraryPage from "./pages/LibraryPage";
+import FillerListsPage from "./pages/FillerListsPage";
+import CustomShowsPage from "./pages/CustomShowsPage";
+import SourcesPage from "./pages/SourcesPage";
+import SystemPage from "./pages/SystemPage";
+import WelcomePage from "./pages/WelcomePage";
+import GeneralSettingsPage from "./pages/settings/GeneralSettingsPage";
+import FfmpegSettingsPage from "./pages/settings/FfmpegSettingsPage";
+import XmltvSettingsPage from "./pages/settings/XmltvSettingsPage";
+import HdhrSettingsPage from "./pages/settings/HdhrSettingsPage";
+import TaskSettingsPage from "./pages/settings/TaskSettingsPage";
export default function App() {
return (
@@ -16,12 +27,23 @@ export default function App() {
}>
} />
+ } />
+ } />
} />
} />
+ } />
+ } />
+ } />
+ } />
} />
} />
} />
- } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
} />
diff --git a/frontend/src/hooks/useAsync.ts b/frontend/src/hooks/useAsync.ts
new file mode 100644
index 0000000..6a95ad2
--- /dev/null
+++ b/frontend/src/hooks/useAsync.ts
@@ -0,0 +1,35 @@
+import { useCallback, useEffect, useRef, useState } from "react";
+
+export type AsyncState =
+ | { status: "idle" }
+ | { status: "loading" }
+ | { status: "success"; data: T }
+ | { status: "error"; error: string };
+
+export function useAsync(
+ fn: (() => Promise) | null,
+ deps: unknown[] = []
+): AsyncState & { reload: () => void } {
+ const [state, setState] = useState>({ status: "idle" });
+ const counter = useRef(0);
+
+ const run = useCallback(() => {
+ if (!fn) return;
+ const id = ++counter.current;
+ setState({ status: "loading" });
+ fn().then(
+ (data) => {
+ if (id === counter.current) setState({ status: "success", data });
+ },
+ (e: unknown) => {
+ if (id === counter.current)
+ setState({ status: "error", error: e instanceof Error ? e.message : String(e) });
+ }
+ );
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [fn, ...deps]);
+
+ useEffect(() => { run(); }, [run]);
+
+ return { ...state, reload: run };
+}
diff --git a/frontend/src/hooks/useQueryData.ts b/frontend/src/hooks/useQueryData.ts
new file mode 100644
index 0000000..2bdf91f
--- /dev/null
+++ b/frontend/src/hooks/useQueryData.ts
@@ -0,0 +1,30 @@
+import { useQuery } from "@tanstack/react-query";
+import { listChannels, getChannel } from "../api/channels";
+import { listSchedules, getSchedule } from "../api/schedules";
+import { listChannelPlayouts, getNowPlaying, listPlayoutItems } from "../api/playouts";
+import { fetchJson } from "../api/client";
+
+export function useChannels() {
+ return useQuery({ queryKey: ["channels"], queryFn: listChannels });
+}
+export function useChannel(id: number) {
+ return useQuery({ queryKey: ["channels", id], queryFn: () => getChannel(id), enabled: Number.isFinite(id) });
+}
+export function useSchedules() {
+ return useQuery({ queryKey: ["schedules"], queryFn: listSchedules });
+}
+export function useSchedule(id: number) {
+ return useQuery({ queryKey: ["schedules", id], queryFn: () => getSchedule(id), enabled: Number.isFinite(id) });
+}
+export function useChannelPlayouts(channelId: number) {
+ return useQuery({ queryKey: ["playouts", channelId], queryFn: () => listChannelPlayouts(channelId), enabled: Number.isFinite(channelId) });
+}
+export function useNowPlaying(playoutId: number | undefined) {
+ return useQuery({ queryKey: ["now-playing", playoutId], queryFn: () => getNowPlaying(playoutId!), enabled: playoutId != null, refetchInterval: 15000 });
+}
+export function usePlayoutItems(playoutId: number | undefined, limit = 15) {
+ return useQuery({ queryKey: ["playout-items", playoutId, limit], queryFn: () => listPlayoutItems(playoutId!, limit, 0), enabled: playoutId != null });
+}
+export function useHealth() {
+ return useQuery({ queryKey: ["health"], queryFn: () => fetchJson>("/api/health"), refetchInterval: 30000 });
+}
diff --git a/frontend/src/index.css b/frontend/src/index.css
index 6bd2092..26c7a48 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -1,7 +1,5 @@
-@tailwind base;
-@tailwind components;
-@tailwind utilities;
-
body {
- @apply antialiased text-slate-100 bg-brand-950 min-h-screen;
+ margin: 0;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
}
diff --git a/frontend/src/layout/AppShell.tsx b/frontend/src/layout/AppShell.tsx
index f47b4b0..18fc662 100644
--- a/frontend/src/layout/AppShell.tsx
+++ b/frontend/src/layout/AppShell.tsx
@@ -1,76 +1,29 @@
-import { NavLink, Outlet } from "react-router-dom";
-import { PERSONAS, usePersona } from "../context/PersonaContext";
-
-const navClass = ({ isActive }: { isActive: boolean }) =>
- [
- "rounded-md px-3 py-2 text-sm font-medium transition",
- isActive
- ? "bg-brand-700 text-white"
- : "text-slate-300 hover:bg-slate-800 hover:text-white",
- ].join(" ");
+import Box from "@mui/material/Box";
+import Toolbar from "@mui/material/Toolbar";
+import { Outlet } from "react-router-dom";
+import TopBar from "./TopBar";
+import SideDrawer from "./SideDrawer";
+import BottomNav from "./BottomNav";
+import { DRAWER_WIDTH } from "./TopBar";
export default function AppShell() {
- const { personaId, setPersonaId } = usePersona();
- const isViewer = personaId === "viewer";
-
return (
-
+
+
+
);
}
diff --git a/frontend/src/layout/BottomNav.tsx b/frontend/src/layout/BottomNav.tsx
new file mode 100644
index 0000000..fb8b393
--- /dev/null
+++ b/frontend/src/layout/BottomNav.tsx
@@ -0,0 +1,55 @@
+import BottomNavigation from "@mui/material/BottomNavigation";
+import BottomNavigationAction from "@mui/material/BottomNavigationAction";
+import Paper from "@mui/material/Paper";
+import TvIcon from "@mui/icons-material/Tv";
+import SettingsRemoteIcon from "@mui/icons-material/SettingsRemote";
+import VideoLibraryIcon from "@mui/icons-material/VideoLibrary";
+import SettingsIcon from "@mui/icons-material/Settings";
+import DashboardIcon from "@mui/icons-material/Dashboard";
+import { useLocation, useNavigate } from "react-router-dom";
+
+const items = [
+ { label: "Home", icon: , path: "/" },
+ { label: "Guide", icon: , path: "/guide" },
+ { label: "Channels", icon: , path: "/channels" },
+ { label: "Library", icon: , path: "/library" },
+ { label: "Settings", icon: , path: "/settings" },
+];
+
+export default function BottomNav() {
+ const location = useLocation();
+ const navigate = useNavigate();
+ const currentIdx = items.findIndex((i) =>
+ i.path === "/"
+ ? location.pathname === "/"
+ : location.pathname.startsWith(i.path)
+ );
+
+ return (
+
+ navigate(items[idx].path)}
+ showLabels
+ >
+ {items.map((item) => (
+
+ ))}
+
+
+ );
+}
diff --git a/frontend/src/layout/SideDrawer.tsx b/frontend/src/layout/SideDrawer.tsx
new file mode 100644
index 0000000..9d4cca9
--- /dev/null
+++ b/frontend/src/layout/SideDrawer.tsx
@@ -0,0 +1,146 @@
+import { useState } from "react";
+import { useLocation, useNavigate } from "react-router-dom";
+import Drawer from "@mui/material/Drawer";
+import Toolbar from "@mui/material/Toolbar";
+import List from "@mui/material/List";
+import ListItemButton from "@mui/material/ListItemButton";
+import ListItemIcon from "@mui/material/ListItemIcon";
+import ListItemText from "@mui/material/ListItemText";
+import Collapse from "@mui/material/Collapse";
+import Divider from "@mui/material/Divider";
+import Typography from "@mui/material/Typography";
+import Box from "@mui/material/Box";
+import TvIcon from "@mui/icons-material/Tv";
+import SettingsRemoteIcon from "@mui/icons-material/SettingsRemote";
+import VideoLibraryIcon from "@mui/icons-material/VideoLibrary";
+import StorageIcon from "@mui/icons-material/Storage";
+import ComputerIcon from "@mui/icons-material/Computer";
+import SettingsIcon from "@mui/icons-material/Settings";
+import ExpandLess from "@mui/icons-material/ExpandLess";
+import ExpandMore from "@mui/icons-material/ExpandMore";
+import ListAltIcon from "@mui/icons-material/ListAlt";
+import DashboardIcon from "@mui/icons-material/Dashboard";
+import { DRAWER_WIDTH } from "./TopBar";
+
+interface NavItem {
+ label: string;
+ path: string;
+ icon: React.ReactNode;
+ children?: { label: string; path: string }[];
+}
+
+const navItems: NavItem[] = [
+ { label: "Dashboard", path: "/", icon: },
+ { label: "Guide", path: "/guide", icon: },
+ { label: "Channels", path: "/channels", icon: },
+ {
+ label: "Library",
+ path: "/library",
+ icon: ,
+ children: [
+ { label: "Filler Lists", path: "/library/fillers" },
+ { label: "Custom Shows", path: "/library/custom-shows" },
+ ],
+ },
+ { label: "Sources", path: "/sources", icon: },
+ { label: "Schedules", path: "/schedules", icon: },
+ { label: "System", path: "/system", icon: },
+ {
+ label: "Settings",
+ path: "/settings",
+ icon: ,
+ children: [
+ { label: "General", path: "/settings" },
+ { label: "FFmpeg", path: "/settings/ffmpeg" },
+ { label: "XMLTV", path: "/settings/xmltv" },
+ { label: "HDHR", path: "/settings/hdhr" },
+ { label: "Tasks", path: "/settings/tasks" },
+ ],
+ },
+];
+
+export default function SideDrawer() {
+ const location = useLocation();
+ const navigate = useNavigate();
+ const [openSections, setOpenSections] = useState>({});
+
+ const toggleSection = (label: string) =>
+ setOpenSections((prev) => ({ ...prev, [label]: !prev[label] }));
+
+ const isActive = (path: string) => {
+ if (path === "/") return location.pathname === "/";
+ return location.pathname.startsWith(path);
+ };
+
+ return (
+
+
+
+ {navItems.map((item) => (
+
+ {
+ if (item.children) {
+ toggleSection(item.label);
+ } else {
+ navigate(item.path);
+ }
+ }}
+ sx={{ borderRadius: 1, mb: 0.5 }}
+ >
+ {item.icon}
+
+ {item.children ? (
+ openSections[item.label] ? (
+
+ ) : (
+
+ )
+ ) : null}
+
+ {item.children ? (
+
+
+ {item.children.map((child) => (
+ navigate(child.path)}
+ sx={{ pl: 6, borderRadius: 1, mb: 0.5 }}
+ >
+
+
+ ))}
+
+
+ ) : null}
+
+ ))}
+
+
+
+
+
+ EXStreamTV v0.1.0
+
+
+
+ );
+}
diff --git a/frontend/src/layout/TopBar.tsx b/frontend/src/layout/TopBar.tsx
new file mode 100644
index 0000000..1acc23d
--- /dev/null
+++ b/frontend/src/layout/TopBar.tsx
@@ -0,0 +1,65 @@
+import AppBar from "@mui/material/AppBar";
+import Toolbar from "@mui/material/Toolbar";
+import Typography from "@mui/material/Typography";
+import IconButton from "@mui/material/IconButton";
+import Select from "@mui/material/Select";
+import MenuItem from "@mui/material/MenuItem";
+import MenuIcon from "@mui/icons-material/Menu";
+import Brightness4Icon from "@mui/icons-material/Brightness4";
+import Brightness7Icon from "@mui/icons-material/Brightness7";
+import { useUIStore } from "../store/uiStore";
+import { PERSONAS, usePersona } from "../context/PersonaContext";
+
+export const DRAWER_WIDTH = 240;
+
+export default function TopBar() {
+ const { themeMode, toggleTheme, setDrawerOpen, drawerOpen } = useUIStore();
+ const { personaId, setPersonaId } = usePersona();
+
+ return (
+ theme.zIndex.drawer + 1,
+ ml: { sm: `${DRAWER_WIDTH}px` },
+ width: { sm: `calc(100% - ${DRAWER_WIDTH}px)` },
+ }}
+ color="default"
+ elevation={0}
+ >
+
+ setDrawerOpen(!drawerOpen)}
+ >
+
+
+
+ EXStreamTV
+
+
+ setPersonaId(e.target.value as typeof personaId)}
+ sx={{ mr: 1, minWidth: 120 }}
+ >
+ {PERSONAS.map((p) => (
+
+ {p}
+
+ ))}
+
+
+ {themeMode === "dark" ? : }
+
+
+
+ );
+}
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
index 65dc4ec..6b9354a 100644
--- a/frontend/src/main.tsx
+++ b/frontend/src/main.tsx
@@ -1,10 +1,35 @@
-import { StrictMode } from "react";
+import { StrictMode, useMemo } from "react";
import { createRoot } from "react-dom/client";
+import { ThemeProvider } from "@mui/material/styles";
+import CssBaseline from "@mui/material/CssBaseline";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { SnackbarProvider } from "notistack";
import App from "./App";
+import { createAppTheme } from "./theme";
+import { useUIStore } from "./store/uiStore";
import "./index.css";
+const queryClient = new QueryClient({
+ defaultOptions: { queries: { retry: 1, staleTime: 30_000 } },
+});
+
+function Root() {
+ const themeMode = useUIStore((s) => s.themeMode);
+ const theme = useMemo(() => createAppTheme(themeMode), [themeMode]);
+ return (
+
+
+
+
+
+
+
+
+ );
+}
+
createRoot(document.getElementById("root")!).render(
-
+
);
diff --git a/frontend/src/pages/ChannelDetailPage.tsx b/frontend/src/pages/ChannelDetailPage.tsx
index 6fd56b6..566fff6 100644
--- a/frontend/src/pages/ChannelDetailPage.tsx
+++ b/frontend/src/pages/ChannelDetailPage.tsx
@@ -1,328 +1,258 @@
-import { useEffect, useMemo, useState } from "react";
+import { useMemo, useState } from "react";
import { Link, useParams } from "react-router-dom";
-import { ApiError } from "../api/client";
-import { getChannel, type ChannelRow } from "../api/channels";
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import Tabs from "@mui/material/Tabs";
+import Tab from "@mui/material/Tab";
+import Card from "@mui/material/Card";
+import CardContent from "@mui/material/CardContent";
+import Grid from "@mui/material/Grid";
+import Table from "@mui/material/Table";
+import TableBody from "@mui/material/TableBody";
+import TableCell from "@mui/material/TableCell";
+import TableContainer from "@mui/material/TableContainer";
+import TableHead from "@mui/material/TableHead";
+import TableRow from "@mui/material/TableRow";
+import Paper from "@mui/material/Paper";
+import Alert from "@mui/material/Alert";
+import CircularProgress from "@mui/material/CircularProgress";
+import Chip from "@mui/material/Chip";
+import MuiLink from "@mui/material/Link";
+import Button from "@mui/material/Button";
import {
- getNowPlaying,
- listChannelPlayouts,
- listPlayoutItems,
- type ChannelPlayoutRow,
- type NowPlayingResponse,
- type PlayoutItemsResponse,
-} from "../api/playouts";
+ useChannel,
+ useChannelPlayouts,
+ useNowPlaying,
+ usePlayoutItems,
+} from "../hooks/useQueryData";
+import type { ChannelPlayoutRow } from "../api/playouts";
-function pickPrimaryPlayout(rows: ChannelPlayoutRow[]): ChannelPlayoutRow | null {
+function pickPrimary(rows: ChannelPlayoutRow[]): ChannelPlayoutRow | null {
if (!rows.length) return null;
- const active = rows.find((p) => p.is_active);
- return active ?? rows[0];
+ return rows.find((p) => p.is_active) ?? rows[0];
}
export default function ChannelDetailPage() {
const { id } = useParams<{ id: string }>();
const numId = id ? Number.parseInt(id, 10) : NaN;
- const [row, setRow] = useState(null);
- const [playouts, setPlayouts] = useState(null);
- const [nowPlaying, setNowPlaying] = useState(null);
- const [timeline, setTimeline] = useState(null);
+ const [tab, setTab] = useState(0);
const [showRaw, setShowRaw] = useState(false);
- const [err, setErr] = useState(null);
- const [subErr, setSubErr] = useState<{
- playouts?: string;
- now?: string;
- items?: string;
- }>({});
+ const { data: row, isLoading: rowLoading, error: rowError } = useChannel(numId);
+ const { data: playouts } = useChannelPlayouts(numId);
const primaryPlayout = useMemo(
- () => (playouts ? pickPrimaryPlayout(playouts) : null),
+ () => (playouts ? pickPrimary(playouts) : null),
[playouts]
);
-
- useEffect(() => {
- if (!Number.isFinite(numId)) {
- setErr("Invalid channel id");
- return;
- }
- let cancelled = false;
- setErr(null);
- (async () => {
- try {
- const data = await getChannel(numId);
- if (!cancelled) setRow(data);
- } catch (e) {
- if (!cancelled) {
- setRow(null);
- setErr(e instanceof ApiError ? e.message : String(e));
- }
- }
- })();
- return () => {
- cancelled = true;
- };
- }, [numId]);
-
- useEffect(() => {
- if (!Number.isFinite(numId) || !row) return;
- let cancelled = false;
- setSubErr((s) => ({ ...s, playouts: undefined }));
- (async () => {
- try {
- const list = await listChannelPlayouts(numId);
- if (!cancelled) setPlayouts(list);
- } catch (e) {
- if (!cancelled) {
- setPlayouts([]);
- setSubErr((s) => ({
- ...s,
- playouts: e instanceof ApiError ? e.message : String(e),
- }));
- }
- }
- })();
- return () => {
- cancelled = true;
- };
- }, [numId, row]);
-
- useEffect(() => {
- if (!primaryPlayout) {
- setNowPlaying(null);
- setTimeline(null);
- return;
- }
- let cancelled = false;
- setSubErr((s) => ({ ...s, now: undefined, items: undefined }));
- (async () => {
- try {
- const np = await getNowPlaying(primaryPlayout.id);
- if (!cancelled) setNowPlaying(np);
- } catch (e) {
- if (!cancelled) {
- setNowPlaying(null);
- setSubErr((s) => ({
- ...s,
- now: e instanceof ApiError ? e.message : String(e),
- }));
- }
- }
- try {
- const items = await listPlayoutItems(primaryPlayout.id, 15, 0);
- if (!cancelled) setTimeline(items);
- } catch (e) {
- if (!cancelled) {
- setTimeline(null);
- setSubErr((s) => ({
- ...s,
- items: e instanceof ApiError ? e.message : String(e),
- }));
- }
- }
- })();
- return () => {
- cancelled = true;
- };
- }, [primaryPlayout]);
+ const { data: nowPlaying } = useNowPlaying(primaryPlayout?.id);
+ const { data: timeline } = usePlayoutItems(primaryPlayout?.id);
if (!Number.isFinite(numId)) {
- return Invalid channel id.
;
- }
-
- if (err) {
- return (
-
-
- ← Channels
-
-
Channel
-
{err}
-
- );
+ return Invalid channel id. ;
}
-
- if (!row) {
+ if (rowLoading) return ;
+ if (rowError) {
return (
-
-
- ← Channels
-
-
Loading…
-
+
+ {rowError instanceof Error ? rowError.message : String(rowError)}
+
);
}
+ if (!row) return null;
return (
-
-
-
- ← Channels
-
-
- {row.name ?? `Channel ${row.id}`}
-
-
- Number {row.number ?? "—"} · id {row.id}
- {row.enabled === false ? " · disabled" : ""}
-
-
+
+
+ ← Channels
+
+
+ {row.name ?? `Channel ${row.id}`}
+
+
+ #{row.number ?? "—"} · id {row.id}
+ {row.enabled === false ? " · disabled" : ""}
+
-
-
- Playouts
-
- {subErr.playouts ? (
- {subErr.playouts}
- ) : playouts === null ? (
- Loading…
- ) : playouts.length === 0 ? (
- No playouts for this channel.
- ) : (
-
-
-
-
- ID
- Type
- Active
- Schedule
-
-
-
- {playouts.map((p) => (
-
- {p.id}
- {p.playout_type ?? "—"}
-
- {p.is_active ? "yes" : "no"}
-
-
- {p.program_schedule_id ?? "—"}
-
-
- ))}
-
-
- {primaryPlayout ? (
-
- Using playout {primaryPlayout.id}
- {primaryPlayout.is_active ? " (active)" : ""} for now-playing
- and timeline.
-
- ) : null}
-
- )}
-
+ setTab(v)} sx={{ mt: 3 }}>
+
+
+
+
- {primaryPlayout ? (
-
-
- Now playing
-
- {subErr.now ? (
- {subErr.now}
- ) : nowPlaying === null ? (
- Loading…
- ) : !nowPlaying.current && !nowPlaying.next ? (
-
- Nothing scheduled at server time{" "}
-
- {nowPlaying.current_time}
-
- .
-
- ) : (
-
-
-
Current
- {nowPlaying.current ? (
- <>
-
- {nowPlaying.current.title ?? "Untitled"}
-
- {nowPlaying.current.episode_title ? (
-
- {nowPlaying.current.episode_title}
-
- ) : null}
-
- {nowPlaying.current.progress_seconds != null &&
- nowPlaying.current.duration_seconds != null
- ? `${Math.round(nowPlaying.current.progress_seconds)}s / ${Math.round(nowPlaying.current.duration_seconds)}s`
- : null}
-
- >
- ) : (
-
—
- )}
-
-
-
Next
- {nowPlaying.next ? (
- <>
-
- {nowPlaying.next.title ?? "Untitled"}
-
- {nowPlaying.next.starts_in_seconds != null ? (
-
- in {Math.round(nowPlaying.next.starts_in_seconds)}s
-
- ) : null}
- >
- ) : (
-
—
- )}
-
-
+ {tab === 0 && (
+
+
+
+ Playouts
+
+ {!playouts ? (
+
+ ) : playouts.length === 0 ? (
+
+ No playouts for this channel.
+
+ ) : (
+
+
+
+
+ ID
+ Type
+ Active
+ Schedule
+
+
+
+ {playouts.map((p) => (
+
+
+ {p.id}
+
+ {p.playout_type ?? "—"}
+
+
+
+ {p.program_schedule_id ?? "—"}
+
+ ))}
+
+
+
+ )}
+
+
+ )}
+
+ {tab === 1 && (
+
+ {primaryPlayout && (
+
+
+
+
+
+ Now Playing
+
+ {!nowPlaying ? (
+
+ ) : nowPlaying.current ? (
+
+
+ {nowPlaying.current.title ?? "Untitled"}
+
+ {nowPlaying.current.episode_title && (
+
+ {nowPlaying.current.episode_title}
+
+ )}
+ {nowPlaying.current.progress_seconds != null && (
+
+ {Math.round(nowPlaying.current.progress_seconds)}s /{" "}
+ {Math.round(nowPlaying.current.duration_seconds ?? 0)}s
+
+ )}
+
+ ) : (
+
+ Nothing playing
+
+ )}
+
+
+
+
+
+
+
+ Up Next
+
+ {nowPlaying?.next ? (
+
+
+ {nowPlaying.next.title ?? "Untitled"}
+
+ {nowPlaying.next.starts_in_seconds != null && (
+
+ in {Math.round(nowPlaying.next.starts_in_seconds)}s
+
+ )}
+
+ ) : (
+
+ —
+
+ )}
+
+
+
+
)}
-
- ) : null}
- {primaryPlayout && timeline ? (
-
-
- Upcoming (first {timeline.count} items)
-
- {subErr.items ? (
- {subErr.items}
- ) : (
-
- {timeline.items.map((it) => (
-
-
- {it.title ?? it.custom_title ?? `Item ${it.id}`}
-
-
- {it.start_time}
-
-
- ))}
-
+ {timeline && timeline.items.length > 0 && (
+
+
+
+ Upcoming ({timeline.count} items)
+
+
+ {timeline.items.map((it) => (
+
+
+ {it.title ?? it.custom_title ?? `Item ${it.id}`}
+
+
+ {it.start_time}
+
+
+ ))}
+
+
+
)}
-
- ) : null}
+
+ )}
-
-
setShowRaw((v) => !v)}
- className="text-sm text-brand-400 hover:underline"
- >
- {showRaw ? "Hide" : "Show"} raw channel JSON
-
- {showRaw ? (
-
- {JSON.stringify(row, null, 2)}
-
- ) : null}
-
-
+ {tab === 2 && (
+
+ setShowRaw((v) => !v)}
+ >
+ {showRaw ? "Hide" : "Show"} raw JSON
+
+ {showRaw && (
+
+
+ {JSON.stringify(row, null, 2)}
+
+
+ )}
+
+ )}
+
);
}
diff --git a/frontend/src/pages/ChannelsPage.tsx b/frontend/src/pages/ChannelsPage.tsx
index 6a4d7c0..69e00b4 100644
--- a/frontend/src/pages/ChannelsPage.tsx
+++ b/frontend/src/pages/ChannelsPage.tsx
@@ -1,73 +1,75 @@
-import { useEffect, useState } from "react";
import { Link } from "react-router-dom";
-import { ApiError } from "../api/client";
-import { listChannels, type ChannelRow } from "../api/channels";
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import Table from "@mui/material/Table";
+import TableBody from "@mui/material/TableBody";
+import TableCell from "@mui/material/TableCell";
+import TableContainer from "@mui/material/TableContainer";
+import TableHead from "@mui/material/TableHead";
+import TableRow from "@mui/material/TableRow";
+import Paper from "@mui/material/Paper";
+import Chip from "@mui/material/Chip";
+import CircularProgress from "@mui/material/CircularProgress";
+import Alert from "@mui/material/Alert";
+import MuiLink from "@mui/material/Link";
+import { useChannels } from "../hooks/useQueryData";
export default function ChannelsPage() {
- const [rows, setRows] = useState(null);
- const [err, setErr] = useState(null);
-
- useEffect(() => {
- let cancelled = false;
- (async () => {
- try {
- const data = await listChannels();
- if (!cancelled) {
- setRows(data);
- setErr(null);
- }
- } catch (e) {
- if (!cancelled) {
- setRows(null);
- setErr(e instanceof ApiError ? e.message : String(e));
- }
- }
- })();
- return () => {
- cancelled = true;
- };
- }, []);
-
- if (err) {
- return (
-
- );
- }
-
- if (!rows) {
- return (
-
- );
- }
+ const { data: rows, isLoading, error } = useChannels();
return (
-
-
Channels
-
{rows.length} channel(s)
-
- {rows.map((c) => (
-
-
-
- {c.name ?? `Channel ${c.id}`}
-
-
- #{c.number ?? c.id}
- {c.enabled === false ? " · disabled" : ""}
-
-
-
- ))}
-
-
+
+
+ Channels
+
+
+ {isLoading ? (
+
+ ) : error ? (
+
+ {error instanceof Error ? error.message : String(error)}
+
+ ) : (
+ <>
+
+ {rows?.length ?? 0} channel(s)
+
+
+
+
+
+ #
+ Name
+ Group
+ Status
+
+
+
+ {rows?.map((c) => (
+
+
+ {c.number ?? c.id}
+
+
+
+ {c.name ?? `Channel ${c.id}`}
+
+
+ {c.group ?? "—"}
+
+
+
+
+ ))}
+
+
+
+ >
+ )}
+
);
}
diff --git a/frontend/src/pages/CustomShowsPage.tsx b/frontend/src/pages/CustomShowsPage.tsx
new file mode 100644
index 0000000..b967d07
--- /dev/null
+++ b/frontend/src/pages/CustomShowsPage.tsx
@@ -0,0 +1,16 @@
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import Alert from "@mui/material/Alert";
+
+export default function CustomShowsPage() {
+ return (
+
+
+ Custom Shows
+
+
+ Custom show management will be available in a future update.
+
+
+ );
+}
diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx
index 8ae31c5..0861ba8 100644
--- a/frontend/src/pages/Dashboard.tsx
+++ b/frontend/src/pages/Dashboard.tsx
@@ -1,86 +1,81 @@
-import { useEffect, useState } from "react";
import { Link } from "react-router-dom";
-import { ApiError, fetchJson } from "../api/client";
-
-type HealthPayload = Record;
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import Card from "@mui/material/Card";
+import CardContent from "@mui/material/CardContent";
+import Grid from "@mui/material/Grid";
+import Chip from "@mui/material/Chip";
+import Alert from "@mui/material/Alert";
+import CircularProgress from "@mui/material/CircularProgress";
+import MuiLink from "@mui/material/Link";
+import { useHealth } from "../hooks/useQueryData";
export default function Dashboard() {
- const [health, setHealth] = useState(null);
- const [err, setErr] = useState(null);
-
- useEffect(() => {
- let cancelled = false;
- (async () => {
- try {
- const h = await fetchJson("/api/health");
- if (!cancelled) {
- setHealth(h);
- setErr(null);
- }
- } catch (e) {
- if (!cancelled) {
- setHealth(null);
- setErr(e instanceof ApiError ? e.message : String(e));
- }
- }
- })();
- return () => {
- cancelled = true;
- };
- }, []);
+ const { data: health, isLoading, error } = useHealth();
return (
-
-
-
Dashboard
-
- React console for EXStreamTV. Start the API on port{" "}
- 8411 and run{" "}
- npm run dev here.
-
-
+
+
+ Dashboard
+
+
+ Start the API on port{" "}
+ 8411 then run{" "}
+ npm run dev in the frontend.
+
-
-
- API health
-
- {err ? (
- {err}
- ) : health ? (
-
- {JSON.stringify(health, null, 2)}
-
- ) : (
- Loading…
- )}
-
+
+
+
+ API Health
+
+ {isLoading ? (
+
+
+
+ ) : error ? (
+
+ {error instanceof Error ? error.message : String(error)}
+
+ ) : (
+
+
+
+ {JSON.stringify(health, null, 2)}
+
+
+ )}
+
+
-
-
-
- Channels
-
-
-
-
- Schedules
-
-
-
-
- Schedule history (memento)
-
-
-
-
+
+ {[
+ { label: "Channels", to: "/channels" },
+ { label: "Schedules", to: "/schedules" },
+ { label: "Schedule History", to: "/schedule-history" },
+ { label: "TV Guide", to: "/guide" },
+ ].map((item) => (
+
+
+
+
+ {item.label}
+
+
+
+
+ ))}
+
+
);
}
diff --git a/frontend/src/pages/FillerListsPage.tsx b/frontend/src/pages/FillerListsPage.tsx
new file mode 100644
index 0000000..472f7b0
--- /dev/null
+++ b/frontend/src/pages/FillerListsPage.tsx
@@ -0,0 +1,16 @@
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import Alert from "@mui/material/Alert";
+
+export default function FillerListsPage() {
+ return (
+
+
+ Filler Lists
+
+
+ Filler list management will be available in a future update.
+
+
+ );
+}
diff --git a/frontend/src/pages/GuidePage.tsx b/frontend/src/pages/GuidePage.tsx
new file mode 100644
index 0000000..d0f9220
--- /dev/null
+++ b/frontend/src/pages/GuidePage.tsx
@@ -0,0 +1,27 @@
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import Card from "@mui/material/Card";
+import CardContent from "@mui/material/CardContent";
+import Alert from "@mui/material/Alert";
+
+export default function GuidePage() {
+ return (
+
+
+ TV Guide
+
+
+ EPG grid view is coming soon. Channel guide data is served via the XMLTV endpoint.
+
+
+
+
+ Access your M3U playlist at{" "}
+ /iptv/playlist.m3u and XMLTV guide at{" "}
+ /iptv/xmltv.xml.
+
+
+
+
+ );
+}
diff --git a/frontend/src/pages/LibraryPage.tsx b/frontend/src/pages/LibraryPage.tsx
new file mode 100644
index 0000000..a785552
--- /dev/null
+++ b/frontend/src/pages/LibraryPage.tsx
@@ -0,0 +1,41 @@
+import { Link } from "react-router-dom";
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import Grid from "@mui/material/Grid";
+import Card from "@mui/material/Card";
+import CardActionArea from "@mui/material/CardActionArea";
+import CardContent from "@mui/material/CardContent";
+import MovieFilterIcon from "@mui/icons-material/MovieFilter";
+import LiveTvIcon from "@mui/icons-material/LiveTv";
+
+const sections = [
+ { label: "Filler Lists", to: "/library/fillers", icon: , desc: "Short clips and bumpers used to fill gaps" },
+ { label: "Custom Shows", to: "/library/custom-shows", icon: , desc: "Manually curated show playlists" },
+];
+
+export default function LibraryPage() {
+ return (
+
+
+ Library
+
+
+ {sections.map((s) => (
+
+
+
+
+ {s.icon}
+
+ {s.label}
+ {s.desc}
+
+
+
+
+
+ ))}
+
+
+ );
+}
diff --git a/frontend/src/pages/ScheduleDetailPage.tsx b/frontend/src/pages/ScheduleDetailPage.tsx
index ac25f85..443cd81 100644
--- a/frontend/src/pages/ScheduleDetailPage.tsx
+++ b/frontend/src/pages/ScheduleDetailPage.tsx
@@ -1,114 +1,68 @@
-import { useEffect, useState } from "react";
import { Link, useParams } from "react-router-dom";
-import { ApiError } from "../api/client";
-import { getSchedule, type ScheduleRow } from "../api/schedules";
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import Card from "@mui/material/Card";
+import CardContent from "@mui/material/CardContent";
+import Stack from "@mui/material/Stack";
+import Chip from "@mui/material/Chip";
+import Alert from "@mui/material/Alert";
+import CircularProgress from "@mui/material/CircularProgress";
+import MuiLink from "@mui/material/Link";
+import { useSchedule } from "../hooks/useQueryData";
export default function ScheduleDetailPage() {
const { id } = useParams<{ id: string }>();
const numId = id ? Number.parseInt(id, 10) : NaN;
- const [row, setRow] = useState(null);
- const [err, setErr] = useState(null);
- useEffect(() => {
- if (!Number.isFinite(numId)) {
- setErr("Invalid schedule id");
- return;
- }
- let cancelled = false;
- (async () => {
- try {
- const data = await getSchedule(numId);
- if (!cancelled) {
- setRow(data);
- setErr(null);
- }
- } catch (e) {
- if (!cancelled) {
- setRow(null);
- setErr(e instanceof ApiError ? e.message : String(e));
- }
- }
- })();
- return () => {
- cancelled = true;
- };
- }, [numId]);
+ const { data: schedule, isLoading, error } = useSchedule(numId);
- if (!Number.isFinite(numId)) {
- return Invalid schedule id.
;
- }
-
- if (err) {
- return (
-
-
- ← Schedules
-
-
Schedule
-
{err}
-
- );
- }
-
- if (!row) {
+ if (!Number.isFinite(numId))
+ return Invalid schedule id. ;
+ if (isLoading) return ;
+ if (error)
return (
-
-
- ← Schedules
-
-
Loading…
-
+
+ {error instanceof Error ? error.message : String(error)}
+
);
- }
-
- const flags = [
- ["Multi-part episodes together", row.keep_multi_part_episodes_together],
- ["Treat collections as shows", row.treat_collections_as_shows],
- ["Shuffle items", row.shuffle_schedule_items],
- ["Random start point", row.random_start_point],
- ] as const;
+ if (!schedule) return null;
return (
-
-
+
+
← Schedules
-
-
- {row.name ?? `Schedule ${row.id}`}
-
- id {row.id}
-
-
-
- Flags
-
-
- {flags.map(([label, v]) => (
-
- {label}:{" "}
-
- {v ? "on" : "off"}
-
-
- ))}
-
-
-
- {(row.created_at || row.updated_at) && (
-
- {row.created_at ? Created {row.created_at}
: null}
- {row.updated_at ? Updated {row.updated_at}
: null}
-
- )}
+
+
+ {schedule.name ?? `Schedule ${schedule.id}`}
+
+
+ id {schedule.id}
+
-
-
- Raw JSON
-
-
- {JSON.stringify(row, null, 2)}
-
-
-
+
+
+
+ Options
+
+
+ {schedule.shuffle_schedule_items && (
+
+ )}
+ {schedule.random_start_point && (
+
+ )}
+ {schedule.keep_multi_part_episodes_together && (
+
+ )}
+ {schedule.treat_collections_as_shows && (
+
+ )}
+ {schedule.is_yaml_source && (
+
+ )}
+
+
+
+
);
}
diff --git a/frontend/src/pages/ScheduleHistoryPage.tsx b/frontend/src/pages/ScheduleHistoryPage.tsx
index f828916..422f2ae 100644
--- a/frontend/src/pages/ScheduleHistoryPage.tsx
+++ b/frontend/src/pages/ScheduleHistoryPage.tsx
@@ -1,155 +1,128 @@
-import { useState, type FormEvent } from "react";
-import { Link } from "react-router-dom";
-import { ApiError } from "../api/client";
-import { captureSnapshot, revertSnapshot } from "../api/scheduleHistory";
+import { useState } from "react";
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import Card from "@mui/material/Card";
+import CardContent from "@mui/material/CardContent";
+import TextField from "@mui/material/TextField";
+import Button from "@mui/material/Button";
+import Stack from "@mui/material/Stack";
+import Alert from "@mui/material/Alert";
import { usePersona } from "../context/PersonaContext";
+import { captureSnapshot, revertSnapshot } from "../api/scheduleHistory";
export default function ScheduleHistoryPage() {
const { personaId } = usePersona();
- const isViewer = personaId === "viewer";
- const [idsText, setIdsText] = useState("");
+ const [channelIds, setChannelIds] = useState("");
const [label, setLabel] = useState("");
- const [busy, setBusy] = useState(false);
- const [msg, setMsg] = useState(null);
const [revertId, setRevertId] = useState("");
+ const [msg, setMsg] = useState<{ kind: "success" | "error"; text: string } | null>(null);
+ const [busy, setBusy] = useState(false);
- async function onCapture(e: FormEvent) {
- e.preventDefault();
- setMsg(null);
- const parts = idsText
- .split(/[,\s]+/)
- .map((s) => s.trim())
- .filter(Boolean);
- const channel_ids = parts.map((p) => Number.parseInt(p, 10));
- if (channel_ids.some((n) => !Number.isFinite(n))) {
- setMsg("Enter numeric channel ids separated by commas or spaces.");
+ async function handleCapture() {
+ const ids = channelIds
+ .split(",")
+ .map((s) => parseInt(s.trim(), 10))
+ .filter(Number.isFinite);
+ if (!ids.length) {
+ setMsg({ kind: "error", text: "Enter at least one channel id." });
return;
}
setBusy(true);
try {
- const res = await captureSnapshot({
- channel_ids,
- persona_id: personaId,
- label: label.trim() || null,
- });
- setMsg(`Captured snapshot id=${res.id}`);
- } catch (err) {
- setMsg(err instanceof ApiError ? err.message : String(err));
+ const res = await captureSnapshot({ channel_ids: ids, persona_id: personaId, label: label || null });
+ setMsg({ kind: "success", text: `Captured snapshot id ${res.id}` });
+ } catch (e) {
+ setMsg({ kind: "error", text: e instanceof Error ? e.message : String(e) });
} finally {
setBusy(false);
}
}
- async function onRevert(e: FormEvent) {
- e.preventDefault();
- setMsg(null);
- const hid = Number.parseInt(revertId, 10);
+ async function handleRevert() {
+ const hid = parseInt(revertId, 10);
if (!Number.isFinite(hid)) {
- setMsg("Enter a numeric history id to revert.");
+ setMsg({ kind: "error", text: "Enter a valid history id." });
return;
}
setBusy(true);
try {
const res = await revertSnapshot(hid, personaId);
- setMsg(
- `Reverted: ${res.status}${res.items_restored != null ? ` (${res.items_restored} items)` : ""}`
- );
- } catch (err) {
- setMsg(err instanceof ApiError ? err.message : String(err));
+ setMsg({ kind: "success", text: `Reverted ${res.items_restored ?? 0} items.` });
+ } catch (e) {
+ setMsg({ kind: "error", text: e instanceof Error ? e.message : String(e) });
} finally {
setBusy(false);
}
}
- if (isViewer) {
- return (
-
-
Schedule history
-
- The viewer persona is read-only. Snapshot capture and
- revert are hidden. Switch to operator or{" "}
- curator in{" "}
-
- Settings
- {" "}
- (persona selector in the header).
-
-
- );
- }
-
return (
-
-
-
Schedule history
-
- Memento capture uses the current Persona from the
- header ({personaId}).
-
-
-
-
-
-
-
- {msg ? (
-
- {msg}
-
- ) : null}
-
+
+
+ Schedule History
+
+ {msg && (
+ setMsg(null)}>
+ {msg.text}
+
+ )}
+
+
+
+
+ Capture Snapshot
+
+
+ setChannelIds(e.target.value)}
+ size="small"
+ fullWidth
+ />
+ setLabel(e.target.value)}
+ size="small"
+ fullWidth
+ />
+
+ Capture
+
+
+
+
+
+
+
+ Revert to Snapshot
+
+
+ setRevertId(e.target.value)}
+ size="small"
+ sx={{ maxWidth: 200 }}
+ />
+
+ Revert
+
+
+
+
+
+
);
}
diff --git a/frontend/src/pages/SchedulesPage.tsx b/frontend/src/pages/SchedulesPage.tsx
index 339e167..917fe17 100644
--- a/frontend/src/pages/SchedulesPage.tsx
+++ b/frontend/src/pages/SchedulesPage.tsx
@@ -1,70 +1,61 @@
-import { useEffect, useState } from "react";
import { Link } from "react-router-dom";
-import { ApiError } from "../api/client";
-import { listSchedules, type ScheduleRow } from "../api/schedules";
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import Table from "@mui/material/Table";
+import TableBody from "@mui/material/TableBody";
+import TableCell from "@mui/material/TableCell";
+import TableContainer from "@mui/material/TableContainer";
+import TableHead from "@mui/material/TableHead";
+import TableRow from "@mui/material/TableRow";
+import Paper from "@mui/material/Paper";
+import CircularProgress from "@mui/material/CircularProgress";
+import Alert from "@mui/material/Alert";
+import MuiLink from "@mui/material/Link";
+import { useSchedules } from "../hooks/useQueryData";
export default function SchedulesPage() {
- const [rows, setRows] = useState(null);
- const [err, setErr] = useState(null);
-
- useEffect(() => {
- let cancelled = false;
- (async () => {
- try {
- const data = await listSchedules();
- if (!cancelled) {
- setRows(data);
- setErr(null);
- }
- } catch (e) {
- if (!cancelled) {
- setRows(null);
- setErr(e instanceof ApiError ? e.message : String(e));
- }
- }
- })();
- return () => {
- cancelled = true;
- };
- }, []);
-
- if (err) {
- return (
-
- );
- }
-
- if (!rows) {
- return (
-
- );
- }
+ const { data: rows, isLoading, error } = useSchedules();
return (
-
-
Schedules
-
{rows.length} schedule(s)
-
- {rows.map((s) => (
-
-
-
- {s.name ?? `Schedule ${s.id}`}
-
- id {s.id}
-
-
- ))}
-
-
+
+
+ Schedules
+
+ {isLoading ? (
+
+ ) : error ? (
+
+ {error instanceof Error ? error.message : String(error)}
+
+ ) : (
+ <>
+
+ {rows?.length ?? 0} schedule(s)
+
+
+
+
+
+ ID
+ Name
+
+
+
+ {rows?.map((s) => (
+
+ {s.id}
+
+
+ {s.name ?? `Schedule ${s.id}`}
+
+
+
+ ))}
+
+
+
+ >
+ )}
+
);
}
diff --git a/frontend/src/pages/SettingsPage.tsx b/frontend/src/pages/SettingsPage.tsx
deleted file mode 100644
index caad743..0000000
--- a/frontend/src/pages/SettingsPage.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-export default function SettingsPage() {
- return (
-
-
Settings
-
-
- Persona is selected in the
- top bar and stored in sessionStorage{" "}
- under exstreamtv.persona_id.
-
-
- API base — In development,
- Vite proxies /api and{" "}
- /iptv to{" "}
- 127.0.0.1:8411. For a split
- deployment, build with{" "}
- VITE_API_BASE pointing at your
- API origin (see src/api/client.ts
- ).
-
-
-
- );
-}
diff --git a/frontend/src/pages/SourcesPage.tsx b/frontend/src/pages/SourcesPage.tsx
new file mode 100644
index 0000000..67caeaf
--- /dev/null
+++ b/frontend/src/pages/SourcesPage.tsx
@@ -0,0 +1,45 @@
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import Grid from "@mui/material/Grid";
+import Card from "@mui/material/Card";
+import CardContent from "@mui/material/CardContent";
+import Chip from "@mui/material/Chip";
+
+const sourceTypes = [
+ { label: "Plex", description: "Connect a Plex Media Server", status: "supported" },
+ { label: "Jellyfin", description: "Connect a Jellyfin server", status: "supported" },
+ { label: "Emby", description: "Connect an Emby server", status: "planned" },
+ { label: "Local Files", description: "Add local media directories", status: "planned" },
+];
+
+export default function SourcesPage() {
+ return (
+
+
+ Media Sources
+
+
+ Configure media server connections for channel programming.
+
+
+ {sourceTypes.map((s) => (
+
+
+
+
+ {s.label}
+ {s.description}
+
+
+
+
+
+ ))}
+
+
+ );
+}
diff --git a/frontend/src/pages/SystemPage.tsx b/frontend/src/pages/SystemPage.tsx
new file mode 100644
index 0000000..b0c7b33
--- /dev/null
+++ b/frontend/src/pages/SystemPage.tsx
@@ -0,0 +1,53 @@
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import Card from "@mui/material/Card";
+import CardContent from "@mui/material/CardContent";
+import Chip from "@mui/material/Chip";
+import CircularProgress from "@mui/material/CircularProgress";
+import Alert from "@mui/material/Alert";
+import { useHealth } from "../hooks/useQueryData";
+
+export default function SystemPage() {
+ const { data: health, isLoading, error } = useHealth();
+
+ return (
+
+
+ System
+
+
+
+
+ API Status
+
+
+ {isLoading ? (
+
+ ) : error ? (
+
+ {error instanceof Error ? error.message : String(error)}
+
+ ) : (
+ <>
+
+
+ {JSON.stringify(health, null, 2)}
+
+ >
+ )}
+
+
+
+
+ );
+}
diff --git a/frontend/src/pages/WelcomePage.tsx b/frontend/src/pages/WelcomePage.tsx
new file mode 100644
index 0000000..db97f57
--- /dev/null
+++ b/frontend/src/pages/WelcomePage.tsx
@@ -0,0 +1,84 @@
+import { useState } from "react";
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import Card from "@mui/material/Card";
+import CardContent from "@mui/material/CardContent";
+import Button from "@mui/material/Button";
+import Stepper from "@mui/material/Stepper";
+import Step from "@mui/material/Step";
+import StepLabel from "@mui/material/StepLabel";
+import { useNavigate } from "react-router-dom";
+import { useUIStore } from "../store/uiStore";
+
+const steps = ["Welcome", "Connect a Source", "Create a Channel", "Done"];
+
+export default function WelcomePage() {
+ const [activeStep, setActiveStep] = useState(0);
+ const navigate = useNavigate();
+ const { dismissWelcome } = useUIStore();
+
+ const handleFinish = () => {
+ dismissWelcome();
+ navigate("/");
+ };
+
+ return (
+
+
+ Welcome to EXStreamTV
+
+
+ {steps.map((label) => (
+
+ {label}
+
+ ))}
+
+
+
+ {activeStep === 0 && (
+
+ EXStreamTV turns your media library into a live TV experience. Set
+ up channels, schedules, and distribute via M3U / XMLTV.
+
+ )}
+ {activeStep === 1 && (
+
+ Connect a Plex or Jellyfin server under{" "}
+ Sources to import your media.
+
+ )}
+ {activeStep === 2 && (
+
+ Create channels under Channels and assign a
+ schedule to start streaming.
+
+ )}
+ {activeStep === 3 && (
+
+ You're all set! Access your playlist at{" "}
+ /iptv/playlist.m3u.
+
+ )}
+
+
+
+ setActiveStep((s) => s - 1)}
+ >
+ Back
+
+ {activeStep < steps.length - 1 ? (
+ setActiveStep((s) => s + 1)}>
+ Next
+
+ ) : (
+
+ Get Started
+
+ )}
+
+
+ );
+}
diff --git a/frontend/src/pages/settings/FfmpegSettingsPage.tsx b/frontend/src/pages/settings/FfmpegSettingsPage.tsx
new file mode 100644
index 0000000..ab9a90f
--- /dev/null
+++ b/frontend/src/pages/settings/FfmpegSettingsPage.tsx
@@ -0,0 +1,16 @@
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import Alert from "@mui/material/Alert";
+
+export default function FfmpegSettingsPage() {
+ return (
+
+
+ Settings — FFmpeg
+
+
+ FFmpeg configuration (executable path, hardware acceleration, default bitrate) will be available in a future update.
+
+
+ );
+}
diff --git a/frontend/src/pages/settings/GeneralSettingsPage.tsx b/frontend/src/pages/settings/GeneralSettingsPage.tsx
new file mode 100644
index 0000000..038c114
--- /dev/null
+++ b/frontend/src/pages/settings/GeneralSettingsPage.tsx
@@ -0,0 +1,46 @@
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import Card from "@mui/material/Card";
+import CardContent from "@mui/material/CardContent";
+import List from "@mui/material/List";
+import ListItem from "@mui/material/ListItem";
+import ListItemText from "@mui/material/ListItemText";
+import Divider from "@mui/material/Divider";
+import { usePersona } from "../../context/PersonaContext";
+
+export default function GeneralSettingsPage() {
+ const { personaId } = usePersona();
+ return (
+
+
+ Settings — General
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/pages/settings/HdhrSettingsPage.tsx b/frontend/src/pages/settings/HdhrSettingsPage.tsx
new file mode 100644
index 0000000..f8ef867
--- /dev/null
+++ b/frontend/src/pages/settings/HdhrSettingsPage.tsx
@@ -0,0 +1,16 @@
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import Alert from "@mui/material/Alert";
+
+export default function HdhrSettingsPage() {
+ return (
+
+
+ Settings — HDHomeRun
+
+
+ HDHomeRun emulation settings will be available in a future update.
+
+
+ );
+}
diff --git a/frontend/src/pages/settings/TaskSettingsPage.tsx b/frontend/src/pages/settings/TaskSettingsPage.tsx
new file mode 100644
index 0000000..e403c16
--- /dev/null
+++ b/frontend/src/pages/settings/TaskSettingsPage.tsx
@@ -0,0 +1,16 @@
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import Alert from "@mui/material/Alert";
+
+export default function TaskSettingsPage() {
+ return (
+
+
+ Settings — Background Tasks
+
+
+ Background task scheduling and monitoring will be available in a future update.
+
+
+ );
+}
diff --git a/frontend/src/pages/settings/XmltvSettingsPage.tsx b/frontend/src/pages/settings/XmltvSettingsPage.tsx
new file mode 100644
index 0000000..0a3b7e6
--- /dev/null
+++ b/frontend/src/pages/settings/XmltvSettingsPage.tsx
@@ -0,0 +1,16 @@
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import Alert from "@mui/material/Alert";
+
+export default function XmltvSettingsPage() {
+ return (
+
+
+ Settings — XMLTV
+
+
+ XMLTV output configuration (programme offset, cache TTL) will be available in a future update.
+
+
+ );
+}
diff --git a/frontend/src/store/uiStore.ts b/frontend/src/store/uiStore.ts
new file mode 100644
index 0000000..74823a5
--- /dev/null
+++ b/frontend/src/store/uiStore.ts
@@ -0,0 +1,26 @@
+import { create } from "zustand";
+import { persist } from "zustand/middleware";
+
+interface UIState {
+ themeMode: "dark" | "light";
+ drawerOpen: boolean;
+ welcomeDismissed: boolean;
+ toggleTheme: () => void;
+ setDrawerOpen: (open: boolean) => void;
+ dismissWelcome: () => void;
+}
+
+export const useUIStore = create()(
+ persist(
+ (set) => ({
+ themeMode: "dark",
+ drawerOpen: true,
+ welcomeDismissed: false,
+ toggleTheme: () =>
+ set((s) => ({ themeMode: s.themeMode === "dark" ? "light" : "dark" })),
+ setDrawerOpen: (open) => set({ drawerOpen: open }),
+ dismissWelcome: () => set({ welcomeDismissed: true }),
+ }),
+ { name: "exstreamtv-ui" }
+ )
+);
diff --git a/frontend/src/theme.ts b/frontend/src/theme.ts
new file mode 100644
index 0000000..5e48230
--- /dev/null
+++ b/frontend/src/theme.ts
@@ -0,0 +1,20 @@
+import { createTheme } from "@mui/material/styles";
+
+export function createAppTheme(mode: "dark" | "light") {
+ return createTheme({
+ palette: {
+ mode,
+ primary: { main: "#3b82f6", light: "#60a5fa", dark: "#1f3c5c" },
+ background:
+ mode === "dark"
+ ? { default: "#0c1222", paper: "#111827" }
+ : { default: "#fafafa", paper: "#ffffff" },
+ },
+ typography: {
+ fontFamily: '"Inter", "Roboto", "Helvetica Neue", Arial, sans-serif',
+ },
+ components: {
+ MuiDrawer: { styleOverrides: { paper: { borderRight: "none" } } },
+ },
+ });
+}
diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js
deleted file mode 100644
index e34fa1d..0000000
--- a/frontend/tailwind.config.js
+++ /dev/null
@@ -1,18 +0,0 @@
-/** @type {import('tailwindcss').Config} */
-export default {
- content: ["./index.html", "./src/**/*.{js,ts,tsx}"],
- theme: {
- extend: {
- colors: {
- brand: {
- 950: "#0c1222",
- 900: "#111827",
- 700: "#1f3c5c",
- 500: "#3b82f6",
- 400: "#60a5fa",
- },
- },
- },
- },
- plugins: [],
-};
diff --git a/mcp_server/server.py b/mcp_server/server.py
index 4b58fac..5c4eee4 100644
--- a/mcp_server/server.py
+++ b/mcp_server/server.py
@@ -94,7 +94,7 @@ def get_project_info() -> dict[str, Any]:
if description:
break
if not description:
- description = "Unified IPTV streaming platform (StreamTV + ErsatzTV)."
+ description = "Unified IPTV streaming platform."
features = [
"Direct online streaming (YouTube, Archive.org)",