Compare commits
37 Commits
develop-go
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| c861a3ba7a | |||
| 476f40a529 | |||
| dbc8b1a82a | |||
| ab9b4f229d | |||
| b435638774 | |||
| 8515498ade | |||
| 43702e8bf7 | |||
| cc3f62a6a1 | |||
| 15b69773f0 | |||
| a80fa755d4 | |||
| 6a1d8b1aee | |||
| 1f8fdad2da | |||
| 5350f421f9 | |||
| 698abcec22 | |||
| b60f65e4d1 | |||
| e854c68ad0 | |||
| b787cd161a | |||
| bd8b21955e | |||
| 87c99e64cd | |||
| baa8811e9e | |||
| fa88fe26b3 | |||
| 90d8409aa9 | |||
| b4bbacd9f1 | |||
| 8b85736903 | |||
| 3beabcfe7f | |||
| 35117b7be9 | |||
| e3587eff71 | |||
| 57903b80b6 | |||
| 5c0ca0e139 | |||
| 9276603a70 | |||
| dc06412f79 | |||
| edc1a33547 | |||
| 3c24da4af8 | |||
| 3491a0a08e | |||
| 6d04f1cbdc | |||
| bbe15d5f3e | |||
| dba9713d96 |
@@ -3,7 +3,16 @@
|
|||||||
"allow": [
|
"allow": [
|
||||||
"Bash(bun run build)",
|
"Bash(bun run build)",
|
||||||
"mcp__ide__getDiagnostics",
|
"mcp__ide__getDiagnostics",
|
||||||
"Bash(bun install:*)"
|
"Bash(bun install:*)",
|
||||||
|
"Bash(bun preview:*)",
|
||||||
|
"Bash(curl:*)",
|
||||||
|
"Bash(python -:*)",
|
||||||
|
"Bash(bun run:*)",
|
||||||
|
"Bash(bunx:*)",
|
||||||
|
"Bash(bun:*)",
|
||||||
|
"Bash(git diff:*)",
|
||||||
|
"Bash(git add:*)",
|
||||||
|
"Bash(git status:*)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
79
.deploy/stream.ui-production.yaml
Normal file
79
.deploy/stream.ui-production.yaml
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: stream.ui-config
|
||||||
|
namespace: stream-production
|
||||||
|
labels:
|
||||||
|
app: stream.ui
|
||||||
|
data:
|
||||||
|
STREAM_API_GRPC_ADDR: "stream.api-svc:9000"
|
||||||
|
GOOGLE_AUTH_FINALIZE_PATH: "/auth/google/finalize"
|
||||||
|
---
|
||||||
|
kind: Service
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
name: stream.ui-svc
|
||||||
|
namespace: stream-production
|
||||||
|
labels:
|
||||||
|
app: stream.ui
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: stream.ui
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 80
|
||||||
|
targetPort: 3000
|
||||||
|
type: NodePort
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: stream.ui-dep
|
||||||
|
namespace: stream-production
|
||||||
|
labels:
|
||||||
|
app: stream.ui
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: stream.ui
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: stream.ui
|
||||||
|
spec:
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: registry-production-secret
|
||||||
|
containers:
|
||||||
|
- name: stream.ui
|
||||||
|
image: registry.awing.vn/stream-production/stream.ui:$BUILD_NUMBER
|
||||||
|
ports:
|
||||||
|
- containerPort: 3000
|
||||||
|
env:
|
||||||
|
- name: STREAM_API_GRPC_ADDR
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: stream.ui-config
|
||||||
|
key: STREAM_API_GRPC_ADDR
|
||||||
|
- name: GOOGLE_AUTH_FINALIZE_PATH
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: stream.ui-config
|
||||||
|
key: GOOGLE_AUTH_FINALIZE_PATH
|
||||||
|
- name: STREAM_INTERNAL_AUTH_MARKER
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: stream.ui-secret
|
||||||
|
key: STREAM_INTERNAL_AUTH_MARKER
|
||||||
|
- name: STREAM_UI_JWT_SECRET
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: stream.ui-secret
|
||||||
|
key: STREAM_UI_JWT_SECRET
|
||||||
|
- name: STREAM_UI_REDIS_URL
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: stream.ui-secret
|
||||||
|
key: STREAM_UI_REDIS_URL
|
||||||
|
|
||||||
68
.dockerignore
Normal file
68
.dockerignore
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# Dependencies
|
||||||
|
node_modules
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Build outputs
|
||||||
|
dist
|
||||||
|
build
|
||||||
|
.rsbuild
|
||||||
|
node_modules
|
||||||
|
/node_modules
|
||||||
|
# Environment files
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
# IDE and editor files
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS generated files
|
||||||
|
.DS_Store
|
||||||
|
.DS_Store?
|
||||||
|
._*
|
||||||
|
.Spotlight-V100
|
||||||
|
.Trashes
|
||||||
|
ehthumbs.db
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Git
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
Dockerfile
|
||||||
|
.dockerignore
|
||||||
|
docker-compose.yml
|
||||||
|
|
||||||
|
# Documentation
|
||||||
|
README.md
|
||||||
|
*.md
|
||||||
|
|
||||||
|
# Test files
|
||||||
|
coverage
|
||||||
|
.coverage
|
||||||
|
.nyc_output
|
||||||
|
test
|
||||||
|
tests
|
||||||
|
__tests__
|
||||||
|
*.test.js
|
||||||
|
*.test.ts
|
||||||
|
*.spec.js
|
||||||
|
*.spec.ts
|
||||||
|
|
||||||
|
# Linting
|
||||||
|
.eslintrc*
|
||||||
|
.prettierrc*
|
||||||
|
.stylelintrc*
|
||||||
|
|
||||||
|
# Other
|
||||||
|
.husky
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# AGENTS.md
|
# AGENTS.md
|
||||||
|
|
||||||
This file provides guidance for AI coding agents working with the Holistream codebase.
|
This file provides guidance for AI coding agents working with the Holistream codebase.
|
||||||
|
hallo
|
||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
**Holistream** is a Vue 3 streaming application with Server-Side Rendering (SSR) deployed on Cloudflare Workers. It provides video upload, management, and streaming capabilities for content creators.
|
**Holistream** is a Vue 3 streaming application with Server-Side Rendering (SSR) deployed on Cloudflare Workers. It provides video upload, management, and streaming capabilities for content creators.
|
||||||
|
|||||||
219
CLAUDE.md
219
CLAUDE.md
@@ -2,197 +2,82 @@
|
|||||||
|
|
||||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
## Project Overview
|
## Project overview
|
||||||
|
|
||||||
Holistream is a Vue 3 streaming application with Server-Side Rendering (SSR) deployed on Cloudflare Workers. It provides video upload, management, and streaming capabilities.
|
`stream-ui` is a Vue 3 SSR frontend deployed on Cloudflare Workers. It uses Hono as the Worker server layer and a custom Vite SSR setup rather than Nuxt.
|
||||||
|
|
||||||
## Technology Stack
|
## Common commands
|
||||||
|
|
||||||
- **Framework**: Vue 3 with JSX/TSX support
|
Run all commands from `stream-ui/`.
|
||||||
- **Router**: Vue Router 5 with SSR-aware history
|
|
||||||
- **Server**: Hono framework on Cloudflare Workers
|
|
||||||
- **Build Tool**: Vite 7 with custom SSR plugin
|
|
||||||
- **Styling**: UnoCSS (Tailwind-like utility-first CSS)
|
|
||||||
- **UI Components**: PrimeVue 4 with Aura theme
|
|
||||||
- **State Management**: Pinia + Pinia Colada for server state
|
|
||||||
- **HTTP Client**: Auto-generated from OpenAPI spec via swagger-typescript-api
|
|
||||||
- **Package Manager**: Bun
|
|
||||||
|
|
||||||
## Common Commands
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Development server with hot reload
|
# Install dependencies
|
||||||
bun dev
|
bun install
|
||||||
|
|
||||||
# Production build (client + worker)
|
# Start local dev server
|
||||||
|
bun run dev
|
||||||
|
|
||||||
|
# Build client + worker bundles
|
||||||
bun run build
|
bun run build
|
||||||
|
|
||||||
# Preview production build locally
|
# Preview production build locally
|
||||||
bun preview
|
bun run preview
|
||||||
|
|
||||||
# Deploy to Cloudflare Workers
|
# Deploy to Cloudflare Workers
|
||||||
bun run deploy
|
bun run deploy
|
||||||
|
|
||||||
# Generate TypeScript types from Wrangler config
|
# Regenerate Cloudflare binding types from Wrangler config
|
||||||
bun run cf-typegen
|
bun run cf-typegen
|
||||||
|
|
||||||
# View Cloudflare Worker logs
|
# Tail Cloudflare Worker logs
|
||||||
bun run tail
|
bun run tail
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note**: The project uses Bun as the package manager. If using npm/yarn, replace `bun` with `npm run` or `yarn`.
|
Notes:
|
||||||
|
- This project uses Bun (`bun.lock` is present).
|
||||||
|
- There is currently no configured `test` script.
|
||||||
|
- There is currently no configured `lint` script.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
### SSR Architecture
|
### SSR entrypoints
|
||||||
|
- `src/index.tsx`: Hono Worker entry; registers middleware, proxy routes, merge/display/manifest routes, then SSR routes
|
||||||
|
- `src/main.ts`: shared app factory for SSR and client hydration
|
||||||
|
- `src/client.ts`: client-side hydration entry
|
||||||
|
- `ssrPlugin.ts`: custom Vite SSR plugin that builds the client first, injects the Vite manifest, and swaps environment-specific modules
|
||||||
|
|
||||||
The app uses a custom SSR setup (`ssrPlugin.ts`) that:
|
### Routing and app structure
|
||||||
- Builds the client bundle FIRST, then the Worker bundle
|
- Routes live in `src/routes/index.ts`.
|
||||||
- Injects the Vite manifest into the server build for asset rendering
|
- Routing is SSR-aware: `createMemoryHistory()` on the server and `createWebHistory()` in the browser.
|
||||||
- Uses environment-based module resolution for `httpClientAdapter` and `liteMqtt`
|
- The app is split into:
|
||||||
|
- public pages
|
||||||
|
- auth pages
|
||||||
|
- protected dashboard/settings pages
|
||||||
|
- Current protected areas include `videos`, `notification`, and `settings/*` routes.
|
||||||
|
|
||||||
Entry points:
|
### State and hydration
|
||||||
- **Server**: `src/index.tsx` - Hono app that renders Vue SSR stream
|
- Pinia is used for app state.
|
||||||
- **Client**: `src/client.ts` - Hydrates the SSR-rendered app
|
- `@pinia/colada` is used for server-state/query hydration.
|
||||||
|
- SSR serializes Pinia state into `$p` and query cache into `$colada`; `src/client.ts` restores both during hydration.
|
||||||
|
- `src/stores/auth.ts` owns session state and route guards depend on `auth.user`.
|
||||||
|
|
||||||
### Module Aliases
|
### API integration
|
||||||
|
- `src/api/client.ts` is generated by `swagger-typescript-api`; do not hand-edit generated sections.
|
||||||
|
- API access should go through the generated client and `@httpClientAdapter`, not raw `fetch`.
|
||||||
|
- `src/api/httpClientAdapter.server.ts` handles SSR-side API calls by forwarding request headers/cookies and proxying frontend `/r/*` requests to `https://api.pipic.fun`.
|
||||||
|
- `src/api/httpClientAdapter.client.ts` is the browser-side adapter.
|
||||||
|
|
||||||
- `@/` → `src/`
|
### Notable flows
|
||||||
- `@httpClientAdapter` → `src/api/httpClientAdapter.server.ts` (SSR) or `.client.ts` (browser)
|
- `src/stores/auth.ts` initializes the logged-in user from `/me` and opens an MQTT connection after login.
|
||||||
- `@liteMqtt` → `src/lib/liteMqtt.server.ts` (SSR) or `.ts` (browser)
|
- `src/composables/useUploadQueue.ts` implements the custom upload queue:
|
||||||
|
- 90MB chunks
|
||||||
|
- max 3 parallel uploads
|
||||||
|
- max 3 retries
|
||||||
|
- max 5 queued items
|
||||||
|
- Styling uses UnoCSS (`uno.config.ts`).
|
||||||
|
|
||||||
### State Management Pattern
|
## Important notes
|
||||||
|
|
||||||
Uses **Pinia Colada** for server state with SSR hydration:
|
- Prefer the actual current code over older documentation when they conflict.
|
||||||
- Queries are fetched server-side and serialized to `window.__APP_DATA__`
|
- The previous version of this file contained stale route and dependency details; verify against `src/routes/index.ts` and `package.json` before assuming old pages or libraries still exist.
|
||||||
- Client hydrates the query cache on startup via `hydrateQueryCache()`
|
- Any frontend change that affects API contracts should be checked against the backend repo (`../stream.api`) as well.
|
||||||
- Pinia state is similarly serialized and restored via `PiniaSharedState` plugin
|
|
||||||
|
|
||||||
### API Client Architecture
|
|
||||||
|
|
||||||
The API client (`src/api/client.ts`) is auto-generated from OpenAPI spec:
|
|
||||||
- Uses `customFetch` adapter that differs between client/server
|
|
||||||
- Server adapter (`httpClientAdapter.server.ts`): Forwards cookies via `hono/context-storage`, merges headers, calls `https://api.pipic.fun`
|
|
||||||
- Client adapter (`httpClientAdapter.client.ts`): Standard fetch with `credentials: "include"`
|
|
||||||
- API proxy route: `/r/*` paths proxy to `https://api.pipic.fun` via `apiProxyMiddleware`
|
|
||||||
- Base API URL constant: `baseAPIURL = "https://api.pipic.fun"`
|
|
||||||
|
|
||||||
### Routing Structure
|
|
||||||
|
|
||||||
Routes are defined in `src/routes/index.ts` with three main layouts:
|
|
||||||
1. **Public** (`/`): Landing page, terms, privacy
|
|
||||||
2. **Auth** (`/login`, `/sign-up`, `/forgot`): Authentication pages (redirects if logged in)
|
|
||||||
3. **Dashboard**: Protected routes requiring auth
|
|
||||||
- `/overview` - Main dashboard
|
|
||||||
- `/upload` - Video upload
|
|
||||||
- `/video` - Video list
|
|
||||||
- `/video/:id` - Video detail/edit
|
|
||||||
- `/payments-and-plans` - Billing
|
|
||||||
- `/notification`, `/profile` - User settings
|
|
||||||
|
|
||||||
Route meta supports `@unhead/vue` for SEO: `meta: { head: { title, meta: [...] } }`
|
|
||||||
|
|
||||||
### Styling System (UnoCSS)
|
|
||||||
|
|
||||||
Configuration in `uno.config.ts`:
|
|
||||||
- **Presets**: Wind4 (Tailwind), Typography, Attributify, Bootstrap buttons
|
|
||||||
- **Custom colors**: `primary` (#14a74b), `accent`, `secondary` (#fd7906), `success`, `info`, `warning`, `danger`
|
|
||||||
- **Shortcuts**: `press-animated` for button press effects
|
|
||||||
- **Transformers**: `transformerCompileClass` (prefix: `_`), `transformerVariantGroup`
|
|
||||||
|
|
||||||
Use `cn()` from `src/lib/utils.ts` for conditional class merging (clsx + tailwind-merge).
|
|
||||||
|
|
||||||
### Component Auto-Import
|
|
||||||
|
|
||||||
Components in `src/components/` are auto-imported via `unplugin-vue-components`:
|
|
||||||
- PrimeVue components resolved via `PrimeVueResolver`
|
|
||||||
- Vue/Pinia/Vue Router APIs auto-imported via `unplugin-auto-import`
|
|
||||||
|
|
||||||
### Auth Flow
|
|
||||||
|
|
||||||
- `useAuthStore` manages auth state with cookie-based sessions
|
|
||||||
- `init()` called on every request to fetch current user via `/me` endpoint
|
|
||||||
- `beforeEach` router guard redirects unauthenticated users from protected routes
|
|
||||||
- MQTT client connects on user login for real-time notifications
|
|
||||||
|
|
||||||
### File Upload Architecture
|
|
||||||
|
|
||||||
Upload queue (`src/composables/useUploadQueue.ts`):
|
|
||||||
- Supports both local files and remote URLs
|
|
||||||
- Presigned POST URLs fetched from API
|
|
||||||
- Parallel chunk upload for large files
|
|
||||||
- Progress tracking with speed calculation
|
|
||||||
- **Chunk configuration**: 90MB chunks, max 3 parallel uploads, max 3 retries
|
|
||||||
- **Upload limits**: Max 5 items in queue
|
|
||||||
- Uses `tmpfiles.org` API for chunk uploads, `/merge` endpoint for finalizing
|
|
||||||
- Cancel support via XHR abort tracking
|
|
||||||
|
|
||||||
### Type Safety
|
|
||||||
|
|
||||||
- TypeScript strict mode enabled
|
|
||||||
- `CloudflareBindings` interface for environment variables (generated via `cf-typegen`)
|
|
||||||
- API types auto-generated from backend OpenAPI spec
|
|
||||||
|
|
||||||
### Environment Variables
|
|
||||||
|
|
||||||
Cloudflare Worker bindings (configured in `wrangler.jsonc`):
|
|
||||||
- No explicit secrets in code - use Wrangler secrets management
|
|
||||||
- `compatibility_date`: "2025-08-03"
|
|
||||||
- `compatibility_flags`: ["nodejs_compat"]
|
|
||||||
|
|
||||||
## Important File Locations
|
|
||||||
|
|
||||||
| Purpose | Path |
|
|
||||||
|---------|------|
|
|
||||||
| Server entry | `src/index.tsx` |
|
|
||||||
| Client entry | `src/client.ts` |
|
|
||||||
| App factory | `src/main.ts` |
|
|
||||||
| Router config | `src/routes/index.ts` |
|
|
||||||
| API client | `src/api/client.ts` |
|
|
||||||
| Auth store | `src/stores/auth.ts` |
|
|
||||||
| SSR plugin | `ssrPlugin.ts` |
|
|
||||||
| UnoCSS config | `uno.config.ts` |
|
|
||||||
| Wrangler config | `wrangler.jsonc` |
|
|
||||||
| Vite config | `vite.config.ts` |
|
|
||||||
|
|
||||||
## Server Structure
|
|
||||||
|
|
||||||
Middleware and routes are organized in `src/server/`:
|
|
||||||
|
|
||||||
**Middlewares** (`src/server/middlewares/`):
|
|
||||||
- `setup.ts` - Global middleware: `contextStorage`, CORS, mobile detection via `is-mobile`
|
|
||||||
- `apiProxy.ts` - Proxies `/r/*` requests to external API
|
|
||||||
|
|
||||||
**Routes** (`src/server/routes/`):
|
|
||||||
- `ssr.ts` - Handles SSR rendering and state serialization
|
|
||||||
- `display.ts`, `merge.ts`, `manifest.ts`, `wellKnown.ts` - API endpoints
|
|
||||||
|
|
||||||
## Development Notes
|
|
||||||
|
|
||||||
- Always use `customFetch` from `@httpClientAdapter` for API calls, never raw fetch
|
|
||||||
- The `honoContext` is provided to Vue app for accessing request context in components
|
|
||||||
- MQTT client in `src/lib/liteMqtt.ts` (using `TinyMqttClient`) handles real-time notifications
|
|
||||||
- Icons are custom Vue components in `src/components/icons/`
|
|
||||||
- Upload indicator is a global component showing queue status
|
|
||||||
- Root component uses error boundary wrapper: `withErrorBoundary(RouterView)` in `src/main.ts`
|
|
||||||
- **Testing & Linting**: There are currently no automated test suites (like Vitest) or linting tools (like ESLint/Prettier) configured.
|
|
||||||
|
|
||||||
## Code Organization
|
|
||||||
|
|
||||||
### Component Structure
|
|
||||||
|
|
||||||
- Keep view components small and focused - extract logical sections into child components
|
|
||||||
- Page views should compose child components, not contain all logic inline
|
|
||||||
- Example: `src/routes/settings/Settings.vue` uses child components in `src/routes/settings/components/`
|
|
||||||
- Components that exceed ~200 lines should be considered for refactoring
|
|
||||||
- Use `components/` subfolder pattern for page-specific components: `src/routes/{feature}/components/`
|
|
||||||
|
|
||||||
### Icons
|
|
||||||
|
|
||||||
- **Use custom SVG icon components** from `src/components/icons/` for UI icons (e.g., `Home`, `Video`, `Bell`, `SettingsIcon`)
|
|
||||||
- Custom icons are Vue components with `filled` prop for active/filled state
|
|
||||||
- PrimeIcons (`pi pi-*` class) should **only** be used for:
|
|
||||||
- Button icons in PrimeVue components (e.g., `icon="pi pi-check"`)
|
|
||||||
- Dialog/action icons where no custom SVG exists
|
|
||||||
- **Do NOT use** `<i class="pi pi-*">` for navigation icons, action buttons, or UI elements that have custom SVG equivalents
|
|
||||||
- When adding new icons, create SVG components in `src/components/icons/` following the existing pattern (support `filled` prop)
|
|
||||||
|
|||||||
39
Dockerfile
Normal file
39
Dockerfile
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# ---------- Builder stage ----------
|
||||||
|
FROM oven/bun:1.3.10-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy lockfiles & package.json
|
||||||
|
COPY package*.json ./
|
||||||
|
COPY bun.lockb* ./
|
||||||
|
COPY yarn.lock* ./
|
||||||
|
COPY pnpm-lock.yaml* ./
|
||||||
|
|
||||||
|
# Install dependencies (cached)
|
||||||
|
RUN --mount=type=cache,target=/root/.bun bun install
|
||||||
|
|
||||||
|
# Copy source
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build app (RSBuild output -> dist)
|
||||||
|
RUN bun run build
|
||||||
|
|
||||||
|
|
||||||
|
# ---------- Production stage ----------
|
||||||
|
FROM oven/bun:1.3.10-alpine AS production
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy built files
|
||||||
|
COPY --from=builder /app/dist ./dist
|
||||||
|
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# Optional health check
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||||
|
CMD wget -qO- http://localhost:3000/ || exit 1
|
||||||
|
|
||||||
|
# Run Bun with fallback install (auto resolves missing deps)
|
||||||
|
CMD [ "bun", "--bun", "dist" ]
|
||||||
605
bun.lock
605
bun.lock
@@ -5,30 +5,43 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "holistream",
|
"name": "holistream",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@pinia/colada": "^0.21.2",
|
"@bufbuild/protobuf": "^2.11.0",
|
||||||
"@unhead/vue": "^2.1.2",
|
"@grpc/grpc-js": "^1.14.3",
|
||||||
"@vueuse/core": "^14.2.0",
|
"@hattip/adapter-node": "^0.0.49",
|
||||||
|
"@hiogawa/tiny-rpc": "^0.2.3-pre.18",
|
||||||
|
"@hiogawa/utils": "^1.7.0",
|
||||||
|
"@hono/node-server": "^1.19.12",
|
||||||
|
"@hono/zod-validator": "^0.7.6",
|
||||||
|
"@pinia/colada": "^1.1.0",
|
||||||
|
"@tanstack/vue-table": "^8.21.3",
|
||||||
|
"@unhead/vue": "^2.1.12",
|
||||||
|
"@vueuse/core": "^14.2.1",
|
||||||
"aws4fetch": "^1.0.20",
|
"aws4fetch": "^1.0.20",
|
||||||
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"hono": "^4.11.7",
|
"hono": "^4.12.9",
|
||||||
|
"i18next": "^26.0.3",
|
||||||
|
"i18next-http-backend": "^3.0.4",
|
||||||
|
"i18next-vue": "^5.4.0",
|
||||||
"is-mobile": "^5.0.0",
|
"is-mobile": "^5.0.0",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"tailwind-merge": "^3.4.0",
|
"superjson": "^2.2.6",
|
||||||
"vue": "^3.5.27",
|
"tailwind-merge": "^3.5.0",
|
||||||
"vue-router": "^5.0.2",
|
"tweetnacl": "^1.0.3",
|
||||||
|
"vue": "^3.5.31",
|
||||||
|
"vue-router": "^5.0.4",
|
||||||
"zod": "^4.3.6",
|
"zod": "^4.3.6",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@cloudflare/vite-plugin": "^1.23.0",
|
"@types/bun": "^1.3.11",
|
||||||
"@types/node": "^25.2.0",
|
"@vitejs/plugin-vue": "^6.0.5",
|
||||||
"@vitejs/plugin-vue": "^6.0.4",
|
"@vitejs/plugin-vue-jsx": "^5.1.5",
|
||||||
"@vitejs/plugin-vue-jsx": "^5.1.4",
|
"estree-walker": "3.0.3",
|
||||||
"unocss": "^66.6.0",
|
"unocss": "^66.6.7",
|
||||||
"unplugin-auto-import": "^21.0.0",
|
"unplugin-auto-import": "^21.0.0",
|
||||||
"unplugin-vue-components": "^31.0.0",
|
"unplugin-vue-components": "^32.0.0",
|
||||||
"vite": "^7.3.1",
|
"vite": "^8.0.3",
|
||||||
"vite-ssr-components": "^0.5.2",
|
"vite-ssr-components": "^0.5.2",
|
||||||
"wrangler": "^4.62.0",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -71,9 +84,9 @@
|
|||||||
|
|
||||||
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
|
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
|
||||||
|
|
||||||
"@babel/helpers": ["@babel/helpers@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="],
|
"@babel/helpers": ["@babel/helpers@7.29.2", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="],
|
||||||
|
|
||||||
"@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
|
"@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="],
|
||||||
|
|
||||||
"@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w=="],
|
"@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w=="],
|
||||||
|
|
||||||
@@ -81,138 +94,48 @@
|
|||||||
|
|
||||||
"@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw=="],
|
"@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw=="],
|
||||||
|
|
||||||
|
"@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="],
|
||||||
|
|
||||||
"@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="],
|
"@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="],
|
||||||
|
|
||||||
"@babel/traverse": ["@babel/traverse@7.29.0", "", { "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" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="],
|
"@babel/traverse": ["@babel/traverse@7.29.0", "", { "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" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="],
|
||||||
|
|
||||||
"@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
|
"@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
|
||||||
|
|
||||||
"@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.4.2", "", {}, "sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ=="],
|
"@bufbuild/protobuf": ["@bufbuild/protobuf@2.11.0", "", {}, "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ=="],
|
||||||
|
|
||||||
"@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.14.0", "", { "peerDependencies": { "unenv": "2.0.0-rc.24", "workerd": "^1.20260218.0" }, "optionalPeers": ["workerd"] }, "sha512-XKAkWhi1nBdNsSEoNG9nkcbyvfUrSjSf+VYVPfOto3gLTZVc3F4g6RASCMh6IixBKCG2yDgZKQIHGKtjcnLnKg=="],
|
"@emnapi/core": ["@emnapi/core@1.9.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" } }, "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA=="],
|
||||||
|
|
||||||
"@cloudflare/vite-plugin": ["@cloudflare/vite-plugin@1.25.5", "", { "dependencies": { "@cloudflare/unenv-preset": "2.14.0", "miniflare": "4.20260302.0", "unenv": "2.0.0-rc.24", "wrangler": "4.68.1", "ws": "8.18.0" }, "peerDependencies": { "vite": "^6.1.0 || ^7.0.0" } }, "sha512-dWnJtp/4/m2XQ5Ssnxrh6rb+Jvlkd9pTZhX8MS5sNhdzoULB6vzPkdKaKnaLnYC97iL3j1I2m0gIr15QznKRjA=="],
|
"@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="],
|
||||||
|
|
||||||
"@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20260302.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-cGtxPByeVrgoqxbmd8qs631wuGwf8yTm/FY44dEW4HdoXrb5jhlE4oWYHFafedkQCvGjY1Vbs3puAiKnuMxTXQ=="],
|
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg=="],
|
||||||
|
|
||||||
"@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20260302.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-WRGqV6RNXM3xoQblJJw1EHKwx9exyhB18cdnToSCUFPObFhk3fzMLoQh7S+nUHUpto6aUrXPVj6R/4G3UPjCxw=="],
|
"@grpc/grpc-js": ["@grpc/grpc-js@1.14.3", "", { "dependencies": { "@grpc/proto-loader": "^0.8.0", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA=="],
|
||||||
|
|
||||||
"@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20260302.0", "", { "os": "linux", "cpu": "x64" }, "sha512-gG423mtUjrmlQT+W2+KisLc6qcGcBLR+QcK5x1gje3bu/dF3oNiYuqY7o58A+sQk6IB849UC4UyNclo1RhP2xw=="],
|
"@grpc/proto-loader": ["@grpc/proto-loader@0.8.0", "", { "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.5.3", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" } }, "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ=="],
|
||||||
|
|
||||||
"@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20260302.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-7M25noGI4WlSBOhrIaY8xZrnn87OQKtJg9YWAO2EFqGjF1Su5QXGaLlQVF4fAKbqTywbHnI8BAuIsIlUSNkhCg=="],
|
"@hattip/adapter-node": ["@hattip/adapter-node@0.0.49", "", { "dependencies": { "@hattip/core": "0.0.49", "@hattip/polyfills": "0.0.49", "@hattip/walk": "0.0.49" } }, "sha512-BE+Y8Q4U0YcH34FZUYU4DssGKOaZLbNL0zK57Z41UZp0m9kS79ZIolBmjjpPhTVpIlRY3Rs+uhXbVXKk7mUcJA=="],
|
||||||
|
|
||||||
"@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20260302.0", "", { "os": "win32", "cpu": "x64" }, "sha512-jK1L3ADkiWxFzlqZTq2iHW1Bd2Nzu1fmMWCGZw4sMZ2W1B2WCm2wHwO2SX/py4BgylyEN3wuF+5zagbkNKht9A=="],
|
"@hattip/core": ["@hattip/core@0.0.49", "", {}, "sha512-3/ZJtC17cv8m6Sph8+nw4exUp9yhEf2Shi7HK6AHSUSBtaaQXZ9rJBVxTfZj3PGNOR/P49UBXOym/52WYKFTJQ=="],
|
||||||
|
|
||||||
"@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="],
|
"@hattip/headers": ["@hattip/headers@0.0.49", "", { "dependencies": { "@hattip/core": "0.0.49" } }, "sha512-rrB2lEhTf0+MNVt5WdW184Ky706F1Ze9Aazn/R8c+/FMUYF9yjem2CgXp49csPt3dALsecrnAUOHFiV0LrrHXA=="],
|
||||||
|
|
||||||
"@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
|
"@hattip/polyfills": ["@hattip/polyfills@0.0.49", "", { "dependencies": { "@hattip/core": "0.0.49", "@whatwg-node/fetch": "^0.9.22", "node-fetch-native": "^1.6.4" } }, "sha512-5g7W5s6Gq+HDxwULGFQ861yAnEx3yd9V8GDwS96HBZ1nM1u93vN+KTuwXvNsV7Z3FJmCrD/pgU8WakvchclYuA=="],
|
||||||
|
|
||||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="],
|
"@hattip/walk": ["@hattip/walk@0.0.49", "", { "dependencies": { "@hattip/headers": "0.0.49", "cac": "^6.7.14", "mime-types": "^2.1.35" }, "bin": { "hattip-walk": "cli.js" } }, "sha512-AgJgKLooZyQnzMfoFg5Mo/aHM+HGBC9ExpXIjNqGimYTRgNbL/K7X5EM1kR2JY90BNKk9lo6Usq1T/nWFdT7TQ=="],
|
||||||
|
|
||||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="],
|
"@hiogawa/tiny-rpc": ["@hiogawa/tiny-rpc@0.2.3-pre.18", "", {}, "sha512-BiNHrutG9G9yV622QvkxZxF+PhkaH2Aspp4/X1KYTfnaQTcg4fFUTBWf5Kf533swon2SuVJwi6U6H1LQbhVOQQ=="],
|
||||||
|
|
||||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="],
|
"@hiogawa/utils": ["@hiogawa/utils@1.7.0", "", {}, "sha512-ghiEFWBR1NENoHn+lSuW7liicTIzVPN+8Srm5UedCTw43gus0mlse6Wp2lz6GmbOXJ/CalMPp/0Tz2X8tajkAg=="],
|
||||||
|
|
||||||
"@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="],
|
"@hono/node-server": ["@hono/node-server@1.19.12", "", { "peerDependencies": { "hono": "^4" } }, "sha512-txsUW4SQ1iilgE0l9/e9VQWmELXifEFvmdA1j6WFh/aFPj99hIntrSsq/if0UWyGVkmrRPKA1wCeP+UCr1B9Uw=="],
|
||||||
|
|
||||||
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="],
|
"@hono/zod-validator": ["@hono/zod-validator@0.7.6", "", { "peerDependencies": { "hono": ">=3.9.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Io1B6d011Gj1KknV4rXYz4le5+5EubcWEU/speUjuw9XMMIaP3n78yXLhjd2A3PXaXaUwEAluOiAyLqhBEJgsw=="],
|
||||||
|
|
||||||
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="],
|
|
||||||
|
|
||||||
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="],
|
|
||||||
|
|
||||||
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="],
|
|
||||||
|
|
||||||
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="],
|
|
||||||
|
|
||||||
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="],
|
|
||||||
|
|
||||||
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="],
|
|
||||||
|
|
||||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="],
|
|
||||||
|
|
||||||
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="],
|
|
||||||
|
|
||||||
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="],
|
|
||||||
|
|
||||||
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="],
|
|
||||||
|
|
||||||
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="],
|
|
||||||
|
|
||||||
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="],
|
|
||||||
|
|
||||||
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="],
|
|
||||||
|
|
||||||
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="],
|
|
||||||
|
|
||||||
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="],
|
|
||||||
|
|
||||||
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="],
|
|
||||||
|
|
||||||
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="],
|
|
||||||
|
|
||||||
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="],
|
|
||||||
|
|
||||||
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="],
|
|
||||||
|
|
||||||
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="],
|
|
||||||
|
|
||||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="],
|
|
||||||
|
|
||||||
"@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="],
|
"@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="],
|
||||||
|
|
||||||
"@iconify/utils": ["@iconify/utils@3.1.0", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "mlly": "^1.8.0" } }, "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw=="],
|
"@iconify/utils": ["@iconify/utils@3.1.0", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "mlly": "^1.8.0" } }, "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw=="],
|
||||||
|
|
||||||
"@img/colour": ["@img/colour@1.0.0", "", {}, "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw=="],
|
|
||||||
|
|
||||||
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="],
|
|
||||||
|
|
||||||
"@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="],
|
|
||||||
|
|
||||||
"@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="],
|
|
||||||
|
|
||||||
"@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="],
|
|
||||||
|
|
||||||
"@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="],
|
|
||||||
|
|
||||||
"@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="],
|
|
||||||
|
|
||||||
"@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="],
|
|
||||||
|
|
||||||
"@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="],
|
|
||||||
|
|
||||||
"@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="],
|
|
||||||
|
|
||||||
"@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="],
|
|
||||||
|
|
||||||
"@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="],
|
|
||||||
|
|
||||||
"@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="],
|
|
||||||
|
|
||||||
"@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="],
|
|
||||||
|
|
||||||
"@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="],
|
|
||||||
|
|
||||||
"@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="],
|
|
||||||
|
|
||||||
"@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="],
|
|
||||||
|
|
||||||
"@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="],
|
|
||||||
|
|
||||||
"@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="],
|
|
||||||
|
|
||||||
"@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="],
|
|
||||||
|
|
||||||
"@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="],
|
|
||||||
|
|
||||||
"@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="],
|
|
||||||
|
|
||||||
"@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="],
|
|
||||||
|
|
||||||
"@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="],
|
|
||||||
|
|
||||||
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="],
|
|
||||||
|
|
||||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
||||||
|
|
||||||
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
|
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
|
||||||
@@ -223,127 +146,173 @@
|
|||||||
|
|
||||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||||
|
|
||||||
"@pinia/colada": ["@pinia/colada@0.21.6", "", { "peerDependencies": { "pinia": "^2.2.6 || ^3.0.0", "vue": "^3.5.17" } }, "sha512-DppfAYky3Uavlpdx2iZHgd/+ZVPyBGTR+x+kFfAUz8h9l1DIQgf2cw/QZg0RZ4GAUNnKf6Ue6FzfWttwqhZXUQ=="],
|
"@js-sdsl/ordered-map": ["@js-sdsl/ordered-map@4.4.2", "", {}, "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw=="],
|
||||||
|
|
||||||
|
"@kamilkisiela/fast-url-parser": ["@kamilkisiela/fast-url-parser@1.1.4", "", {}, "sha512-gbkePEBupNydxCelHCESvFSFM8XPh1Zs/OAVRW/rKpEqPAl5PbOM90Si8mv9bvnR53uPD2s/FiRxdvSejpRJew=="],
|
||||||
|
|
||||||
|
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="],
|
||||||
|
|
||||||
|
"@oxc-parser/binding-android-arm-eabi": ["@oxc-parser/binding-android-arm-eabi@0.115.0", "", { "os": "android", "cpu": "arm" }, "sha512-VoB2rhgoqgYf64d6Qs5emONQW8ASiTc0xp+aUE4JUhxjX+0pE3gblTYDO0upcN5vt9UlBNmUhAwfSifkfre7nw=="],
|
||||||
|
|
||||||
|
"@oxc-parser/binding-android-arm64": ["@oxc-parser/binding-android-arm64@0.115.0", "", { "os": "android", "cpu": "arm64" }, "sha512-lWRX75u+gqfB4TF3pWCHuvhaeneAmRl2b2qNBcl4S6yJ0HtnT4VXOMEZrq747i4Zby1ZTxj6mtOe678Bg8gRLw=="],
|
||||||
|
|
||||||
|
"@oxc-parser/binding-darwin-arm64": ["@oxc-parser/binding-darwin-arm64@0.115.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ii/oOZjfGY1aszXTy29Z5DRyCEnBOrAXDVCvfdfXFQsOZlbbOa7NMHD7D+06YFe5qdxfmbWAYv4yn6QJi/0d2g=="],
|
||||||
|
|
||||||
|
"@oxc-parser/binding-darwin-x64": ["@oxc-parser/binding-darwin-x64@0.115.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-R/sW/p8l77wglbjpMcF+h/3rWbp9zk1mRP3U14mxTYIC2k3m+aLBpXXgk2zksqf9qKk5mcc4GIYsuCn9l8TgDg=="],
|
||||||
|
|
||||||
|
"@oxc-parser/binding-freebsd-x64": ["@oxc-parser/binding-freebsd-x64@0.115.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-CSJ5ldNm9wIGGkhaIJeGmxRMZbgxThRN+X1ufYQQUNi5jZDV/U3C2QDMywpP93fczNBj961hXtcUPO/oVGq4Pw=="],
|
||||||
|
|
||||||
|
"@oxc-parser/binding-linux-arm-gnueabihf": ["@oxc-parser/binding-linux-arm-gnueabihf@0.115.0", "", { "os": "linux", "cpu": "arm" }, "sha512-uWFwssE5dHfQ8lH+ktrsD9JA49+Qa0gtxZHUs62z1e91NgGz6O7jefHGI6aygNyKNS45pnnBSDSP/zV977MsOQ=="],
|
||||||
|
|
||||||
|
"@oxc-parser/binding-linux-arm-musleabihf": ["@oxc-parser/binding-linux-arm-musleabihf@0.115.0", "", { "os": "linux", "cpu": "arm" }, "sha512-fZbqt8y/sKQ+v6bBCuv/mYYFoC0+fZI3mGDDEemmDOhT78+aUs2+4ZMdbd2btlXmnLaScl37r8IRbhnok5Ka9w=="],
|
||||||
|
|
||||||
|
"@oxc-parser/binding-linux-arm64-gnu": ["@oxc-parser/binding-linux-arm64-gnu@0.115.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-1ej/MjuTY9tJEunU/hUPIFmgH5PqgMQoRjNOvOkibtJ3Zqlw/+Lc+HGHDNET8sjbgIkWzdhX+p4J96A5CPdbag=="],
|
||||||
|
|
||||||
|
"@oxc-parser/binding-linux-arm64-musl": ["@oxc-parser/binding-linux-arm64-musl@0.115.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-HjsZbJPH9mMd4swJRywVMsDZsJX0hyKb1iNHo5ijRl5yhtbO3lj7ImSrrL1oZ1VEg0te4iKmDGGz/6YPLd1G8w=="],
|
||||||
|
|
||||||
|
"@oxc-parser/binding-linux-ppc64-gnu": ["@oxc-parser/binding-linux-ppc64-gnu@0.115.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-zhhePoBrd7kQx3oClX/W6NldsuCbuMqaN9rRsY+6/WoorAb4j490PG/FjqgAXscWp2uSW2WV9L+ksn0wHrvsrg=="],
|
||||||
|
|
||||||
|
"@oxc-parser/binding-linux-riscv64-gnu": ["@oxc-parser/binding-linux-riscv64-gnu@0.115.0", "", { "os": "linux", "cpu": "none" }, "sha512-t/IRojvUE9XrKu+/H1b8YINug+7Q6FLls5rsm2lxB5mnS8GN/eYAYrPgHkcg9/1SueRDSzGpDYu3lGWTObk1zw=="],
|
||||||
|
|
||||||
|
"@oxc-parser/binding-linux-riscv64-musl": ["@oxc-parser/binding-linux-riscv64-musl@0.115.0", "", { "os": "linux", "cpu": "none" }, "sha512-79jBHSSh/YpQRAmvYoaCfpyToRbJ/HBrdB7hxK2ku2JMehjopTVo+xMJss/RV7/ZYqeezgjvKDQzapJbgcjVZA=="],
|
||||||
|
|
||||||
|
"@oxc-parser/binding-linux-s390x-gnu": ["@oxc-parser/binding-linux-s390x-gnu@0.115.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-nA1TpxkhNTIOMMyiSSsa7XIVJVoOU/SsVrHIz3gHvWweB5PHCQfO7w+Lb2EP0lBWokv7HtA/KbF7aLDoXzmuMw=="],
|
||||||
|
|
||||||
|
"@oxc-parser/binding-linux-x64-gnu": ["@oxc-parser/binding-linux-x64-gnu@0.115.0", "", { "os": "linux", "cpu": "x64" }, "sha512-9iVX789DoC3SaOOG+X6NcF/tVChgLp2vcHffzOC2/Z1JTPlz6bMG2ogvcW6/9s0BG2qvhNQImd+gbWYeQbOwVw=="],
|
||||||
|
|
||||||
|
"@oxc-parser/binding-linux-x64-musl": ["@oxc-parser/binding-linux-x64-musl@0.115.0", "", { "os": "linux", "cpu": "x64" }, "sha512-RmQmk+mjCB0nMNfEYhaCxwofLo1Z95ebHw1AGvRiWGCd4zhCNOyskgCbMogIcQzSB3SuEKWgkssyaiQYVAA4hQ=="],
|
||||||
|
|
||||||
|
"@oxc-parser/binding-openharmony-arm64": ["@oxc-parser/binding-openharmony-arm64@0.115.0", "", { "os": "none", "cpu": "arm64" }, "sha512-viigraWWQhhDvX5aGq+wrQq58k00Xq3MHz/0R4AFMxGlZ8ogNonpEfNc73Q5Ly87Z6sU9BvxEdG0dnYTfVnmew=="],
|
||||||
|
|
||||||
|
"@oxc-parser/binding-wasm32-wasi": ["@oxc-parser/binding-wasm32-wasi@0.115.0", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-IzGCrMwXhpb4kTXy/8lnqqqwjI7eOvy+r9AhVw+hsr8t1ecBBEHprcNy0aKatFHN6hsX7UMHHQmBAQjVvL/p1A=="],
|
||||||
|
|
||||||
|
"@oxc-parser/binding-win32-arm64-msvc": ["@oxc-parser/binding-win32-arm64-msvc@0.115.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-/ym+Absk/TLFvbhh3se9XYuI1D7BrUVHw4RaG/2dmWKgBenrZHaJsgnRb7NJtaOyjEOLIPtULx1wDdVL0SX2eg=="],
|
||||||
|
|
||||||
|
"@oxc-parser/binding-win32-ia32-msvc": ["@oxc-parser/binding-win32-ia32-msvc@0.115.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-AQSZjIR+b+Te7uaO/hGTMjT8/oxlYrvKrOTi4KTHF/O6osjHEatUQ3y6ZW2+8+lJxy20zIcGz6iQFmFq/qDKkg=="],
|
||||||
|
|
||||||
|
"@oxc-parser/binding-win32-x64-msvc": ["@oxc-parser/binding-win32-x64-msvc@0.115.0", "", { "os": "win32", "cpu": "x64" }, "sha512-oxUl82N+fIO9jIaXPph8SPPHQXrA08BHokBBJW8ct9F/x6o6bZE6eUAhUtWajbtvFhL8UYcCWRMba+kww6MBlA=="],
|
||||||
|
|
||||||
|
"@oxc-project/types": ["@oxc-project/types@0.122.0", "", {}, "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA=="],
|
||||||
|
|
||||||
|
"@pinia/colada": ["@pinia/colada@1.1.0", "", { "peerDependencies": { "pinia": "^2.2.6 || ^3.0.0", "vue": "^3.5.17" } }, "sha512-GoTOlaDuQuF+9Lj5MbY2oOOZeJSP/I0U9/+Ugo5nfCymlWSyjFFjE7QcHageNL7tQr+tfepb13ks8+qO6uSV/A=="],
|
||||||
|
|
||||||
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
|
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
|
||||||
|
|
||||||
"@poppinss/colors": ["@poppinss/colors@4.1.6", "", { "dependencies": { "kleur": "^4.1.5" } }, "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg=="],
|
"@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="],
|
||||||
|
|
||||||
"@poppinss/dumper": ["@poppinss/dumper@0.6.5", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@sindresorhus/is": "^7.0.2", "supports-color": "^10.0.0" } }, "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw=="],
|
"@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="],
|
||||||
|
|
||||||
"@poppinss/exception": ["@poppinss/exception@1.2.3", "", {}, "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw=="],
|
"@protobufjs/codegen": ["@protobufjs/codegen@2.0.4", "", {}, "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="],
|
||||||
|
|
||||||
|
"@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.0", "", {}, "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="],
|
||||||
|
|
||||||
|
"@protobufjs/fetch": ["@protobufjs/fetch@1.1.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" } }, "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ=="],
|
||||||
|
|
||||||
|
"@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="],
|
||||||
|
|
||||||
|
"@protobufjs/inquire": ["@protobufjs/inquire@1.1.0", "", {}, "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="],
|
||||||
|
|
||||||
|
"@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="],
|
||||||
|
|
||||||
|
"@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="],
|
||||||
|
|
||||||
|
"@protobufjs/utf8": ["@protobufjs/utf8@1.1.0", "", {}, "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="],
|
||||||
|
|
||||||
"@quansync/fs": ["@quansync/fs@1.0.0", "", { "dependencies": { "quansync": "^1.0.0" } }, "sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ=="],
|
"@quansync/fs": ["@quansync/fs@1.0.0", "", { "dependencies": { "quansync": "^1.0.0" } }, "sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.12", "", { "os": "android", "cpu": "arm64" }, "sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.12", "", { "os": "linux", "cpu": "arm" }, "sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.12", "", { "os": "linux", "cpu": "x64" }, "sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.12", "", { "os": "linux", "cpu": "x64" }, "sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.12", "", { "os": "none", "cpu": "arm64" }, "sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.12", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.12", "", { "os": "win32", "cpu": "x64" }, "sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw=="],
|
||||||
|
|
||||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.2", "", {}, "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw=="],
|
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.2", "", {}, "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw=="],
|
||||||
|
|
||||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.59.0", "", { "os": "android", "cpu": "arm" }, "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg=="],
|
"@tanstack/table-core": ["@tanstack/table-core@8.21.3", "", {}, "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg=="],
|
||||||
|
|
||||||
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.59.0", "", { "os": "android", "cpu": "arm64" }, "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q=="],
|
"@tanstack/vue-table": ["@tanstack/vue-table@8.21.3", "", { "dependencies": { "@tanstack/table-core": "8.21.3" }, "peerDependencies": { "vue": ">=3.2" } }, "sha512-rusRyd77c5tDPloPskctMyPLFEQUeBzxdQ+2Eow4F7gDPlPOB1UnnhzfpdvqZ8ZyX2rRNGmqNnQWm87OI2OQPw=="],
|
||||||
|
|
||||||
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.59.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg=="],
|
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||||
|
|
||||||
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.59.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w=="],
|
"@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="],
|
||||||
|
|
||||||
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.59.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.59.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.59.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.59.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.59.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.59.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.59.0", "", { "os": "none", "cpu": "arm64" }, "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.59.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.59.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA=="],
|
|
||||||
|
|
||||||
"@sindresorhus/is": ["@sindresorhus/is@7.2.0", "", {}, "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw=="],
|
|
||||||
|
|
||||||
"@speed-highlight/core": ["@speed-highlight/core@1.2.14", "", {}, "sha512-G4ewlBNhUtlLvrJTb88d2mdy2KRijzs4UhnlrOSRT4bmjh/IqNElZa3zkrZ+TC47TwtlDWzVLFADljF1Ijp5hA=="],
|
|
||||||
|
|
||||||
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||||
|
|
||||||
"@types/node": ["@types/node@25.3.1", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-hj9YIJimBCipHVfHKRMnvmHg+wfhKc0o4mTtXh9pKBjC8TLJzz0nzGmLi5UJsYAUgSvXFHgb0V2oY10DUFtImw=="],
|
"@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="],
|
||||||
|
|
||||||
"@types/web-bluetooth": ["@types/web-bluetooth@0.0.21", "", {}, "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA=="],
|
"@types/web-bluetooth": ["@types/web-bluetooth@0.0.21", "", {}, "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA=="],
|
||||||
|
|
||||||
"@unhead/vue": ["@unhead/vue@2.1.9", "", { "dependencies": { "hookable": "^6.0.1", "unhead": "2.1.9" }, "peerDependencies": { "vue": ">=3.5.18" } }, "sha512-7SqqDEn5zFID1PnEdjLCLa/kOhoAlzol0JdYfVr2Ejek+H4ON4s8iyExv2QQ8bReMosbXQ/Bw41j2CF1NUuGSA=="],
|
"@unhead/vue": ["@unhead/vue@2.1.12", "", { "dependencies": { "hookable": "^6.0.1", "unhead": "2.1.12" }, "peerDependencies": { "vue": ">=3.5.18" } }, "sha512-zEWqg0nZM8acpuTZE40wkeUl8AhIe0tU0OkilVi1D4fmVjACrwoh5HP6aNqJ8kUnKsoy6D+R3Vi/O+fmdNGO7g=="],
|
||||||
|
|
||||||
"@unocss/cli": ["@unocss/cli@66.6.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "@unocss/config": "66.6.2", "@unocss/core": "66.6.2", "@unocss/preset-wind3": "66.6.2", "@unocss/preset-wind4": "66.6.2", "@unocss/transformer-directives": "66.6.2", "cac": "^6.7.14", "chokidar": "^5.0.0", "colorette": "^2.0.20", "consola": "^3.4.2", "magic-string": "^0.30.21", "pathe": "^2.0.3", "perfect-debounce": "^2.1.0", "tinyglobby": "^0.2.15", "unplugin-utils": "^0.3.1" }, "bin": { "unocss": "bin/unocss.mjs" } }, "sha512-N7nKnOJ/36FRs3PE7+CFbzg7UBhIsucYYAK5xjJScX0H2q8O6rODaNM5uvc77Qh4q+y1S/Bt5ArOwIewzdpP4w=="],
|
"@unocss/cli": ["@unocss/cli@66.6.7", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "@unocss/config": "66.6.7", "@unocss/core": "66.6.7", "@unocss/preset-wind3": "66.6.7", "@unocss/preset-wind4": "66.6.7", "@unocss/transformer-directives": "66.6.7", "cac": "^6.7.14", "chokidar": "^5.0.0", "colorette": "^2.0.20", "consola": "^3.4.2", "magic-string": "^0.30.21", "pathe": "^2.0.3", "perfect-debounce": "^2.1.0", "tinyglobby": "^0.2.15", "unplugin-utils": "^0.3.1" }, "bin": { "unocss": "bin/unocss.mjs" } }, "sha512-m/yW5HMVyxfAOeyO4OyA4JB9dY+/gTsk25ucI8xVCFVDEENPEGr+vEqTDOA+vfe6pdURtyDYS7OrhikIRU1WNA=="],
|
||||||
|
|
||||||
"@unocss/config": ["@unocss/config@66.6.2", "", { "dependencies": { "@unocss/core": "66.6.2", "colorette": "^2.0.20", "consola": "^3.4.2", "unconfig": "^7.5.0" } }, "sha512-qny2bRW1OA+MZbWShVZdBg6fJundm1LqQwCxJnIpeK3McpPHS3pnHBiwD1wfZHY2z5Pe+XgZOZkozNmG/eyyqg=="],
|
"@unocss/config": ["@unocss/config@66.6.7", "", { "dependencies": { "@unocss/core": "66.6.7", "colorette": "^2.0.20", "consola": "^3.4.2", "unconfig": "^7.5.0" } }, "sha512-1uleyRLyJc6PNNc2L3hEaKL89zXwvQAtP36oFySgL47RAxZHPZ4vfqFpbwR0eEN4iSqTS24ZFr7CTRWCaEGjzQ=="],
|
||||||
|
|
||||||
"@unocss/core": ["@unocss/core@66.6.2", "", {}, "sha512-IOvN1BLRP0VTjjS5afSxmXhvKRDko2Shisp8spU+A9qiH1tXEFP3phyVevm/SuGwBHO1lC+SJ451/4oFkCAwJA=="],
|
"@unocss/core": ["@unocss/core@66.6.7", "", {}, "sha512-Q8456iWFtdwrUNYKVOQY8ygRggjZOVtLc6Jc8KIkxig7OiNlUWOgXJTfCh4I8g6jBYzC5eHaHFDLgJOmOrxBsg=="],
|
||||||
|
|
||||||
"@unocss/extractor-arbitrary-variants": ["@unocss/extractor-arbitrary-variants@66.6.2", "", { "dependencies": { "@unocss/core": "66.6.2" } }, "sha512-D2tK/8QClrVViSuoH5eLjXwlVOK1UgXx7ukz/D260+R6vhCmjv97RXPouZkq40sxGzfxzaQZUyPEjXLjtnO3bw=="],
|
"@unocss/extractor-arbitrary-variants": ["@unocss/extractor-arbitrary-variants@66.6.7", "", { "dependencies": { "@unocss/core": "66.6.7" } }, "sha512-PQiBHK0yUJ0BR+3GYnTPU6va6HVSRPV+O+s1zZmt23TWbyIeucoKCNR47TDtv+Z1xuksY8krIjtDYtufdrVWKw=="],
|
||||||
|
|
||||||
"@unocss/inspector": ["@unocss/inspector@66.6.2", "", { "dependencies": { "@unocss/core": "66.6.2", "@unocss/rule-utils": "66.6.2", "colorette": "^2.0.20", "gzip-size": "^6.0.0", "sirv": "^3.0.2" } }, "sha512-q0kktb01dXeeXyNnNwYM1SkSHxrEOQhCZ/YQ5aCdC7BWNGF4yZMK0YrJXmGUTEHN4RhEPLN/rAIsDBsKcoFaAQ=="],
|
"@unocss/inspector": ["@unocss/inspector@66.6.7", "", { "dependencies": { "@unocss/core": "66.6.7", "@unocss/rule-utils": "66.6.7", "colorette": "^2.0.20", "gzip-size": "^6.0.0", "sirv": "^3.0.2" } }, "sha512-4lA70A/wy9dfSDm7rJ5Uq5fKz+/Szm2rUcHjdbLCVNEc6vv2YXeI7aFvP5qDjTp4ClBSF2AMPnF1mtoMQOfDvA=="],
|
||||||
|
|
||||||
"@unocss/preset-attributify": ["@unocss/preset-attributify@66.6.2", "", { "dependencies": { "@unocss/core": "66.6.2" } }, "sha512-pRry38qO1kJvj5/cekbDk0QLosty+UFQ3fhNiph88D//jkT5tsUCn77nB/RTSe7oTqw/FqNwxPgbGz/wfNWqZg=="],
|
"@unocss/preset-attributify": ["@unocss/preset-attributify@66.6.7", "", { "dependencies": { "@unocss/core": "66.6.7" } }, "sha512-thtoLQb53+Acy2QJYT6n+YhgNJ5ilhS8k9bqi+UzflbsuK4TJqOuQQjC9fRkULP5QjtNxgqN3d5Up7ms8tBPDA=="],
|
||||||
|
|
||||||
"@unocss/preset-icons": ["@unocss/preset-icons@66.6.2", "", { "dependencies": { "@iconify/utils": "^3.1.0", "@unocss/core": "66.6.2", "ofetch": "^1.5.1" } }, "sha512-FjhxvYX+21HefYdMIxJCq8C9v/K7fSlO1DMqDQgtrCp0/WvHyFncHILLOwp064M7m3AqzOVJx7Vw/zCvKy0Jrg=="],
|
"@unocss/preset-icons": ["@unocss/preset-icons@66.6.7", "", { "dependencies": { "@iconify/utils": "^3.1.0", "@unocss/core": "66.6.7", "ofetch": "^1.5.1" } }, "sha512-mGAOyI/qz1pZUV1BcOtWAMm5czdFCjhFCYcDk0KY+Jw37pKRVSQRFeh4gpHuYKmehGv36caLyVrWXpTAwRBdFQ=="],
|
||||||
|
|
||||||
"@unocss/preset-mini": ["@unocss/preset-mini@66.6.2", "", { "dependencies": { "@unocss/core": "66.6.2", "@unocss/extractor-arbitrary-variants": "66.6.2", "@unocss/rule-utils": "66.6.2" } }, "sha512-mybpiAq9htF7PWPH1Mnb4y7hrxVwpsBg8VfbjSglY3SfLca8RrJtvBT+DVh7YUDRiYsZGfihRWkfD0AN68gkcA=="],
|
"@unocss/preset-mini": ["@unocss/preset-mini@66.6.7", "", { "dependencies": { "@unocss/core": "66.6.7", "@unocss/extractor-arbitrary-variants": "66.6.7", "@unocss/rule-utils": "66.6.7" } }, "sha512-tf0mqiSEhPQ49WZOqjNhxlbZbNakiBLzCoxfLSzqfIGglOPYShP8mxsdp9Jv0n+Ntn0rHcBiX5KTLfax1/Bd9g=="],
|
||||||
|
|
||||||
"@unocss/preset-tagify": ["@unocss/preset-tagify@66.6.2", "", { "dependencies": { "@unocss/core": "66.6.2" } }, "sha512-ybb45So2x87P3bssLRp1uIS+VHAeNSecwkHqiv93PnuBDJ38/9XlqWF98uga2MEfNM3zvMj9plX9MauidxiPrw=="],
|
"@unocss/preset-tagify": ["@unocss/preset-tagify@66.6.7", "", { "dependencies": { "@unocss/core": "66.6.7" } }, "sha512-0WeQf+Dx9Ztv3aewkBKEnAfOauSjvWBlfkpsgLpXcCkyGMnCqq87UrAq3+b76TDJvQc8i2ADlvVGK7V1z0JZQg=="],
|
||||||
|
|
||||||
"@unocss/preset-typography": ["@unocss/preset-typography@66.6.2", "", { "dependencies": { "@unocss/core": "66.6.2", "@unocss/rule-utils": "66.6.2" } }, "sha512-1f/ZfeuLQOnO48mRz1+6UdoJxa13ZYcamaLz7ft96n7D1eWvkOUAC/AUUke/kbHh3vvqwRVimC9OpdXxdGFQAQ=="],
|
"@unocss/preset-typography": ["@unocss/preset-typography@66.6.7", "", { "dependencies": { "@unocss/core": "66.6.7", "@unocss/rule-utils": "66.6.7" } }, "sha512-RA7MwPDD5N9xGrbWnguVm5tP+F4/n/9X1rJsq2nBjvvK2dbtIRJZjRFM1vBDsR0GIhtvbHMoTchZaSZed5I+Hw=="],
|
||||||
|
|
||||||
"@unocss/preset-uno": ["@unocss/preset-uno@66.6.2", "", { "dependencies": { "@unocss/core": "66.6.2", "@unocss/preset-wind3": "66.6.2" } }, "sha512-Wy3V25ZF29OmVHJk5ghP6HCCRNBJXm0t+bKLKJJknOjD+/D51DZbUsDqZBtTpVtgi/SOPDbw7cX3lY2oqt4Hnw=="],
|
"@unocss/preset-uno": ["@unocss/preset-uno@66.6.7", "", { "dependencies": { "@unocss/core": "66.6.7", "@unocss/preset-wind3": "66.6.7" } }, "sha512-imGCe6Yv2XgrJxP77gV8WZCz0xL99MsGov5rYn64lh2/tcsHF2rUIhTj/Urgxt0kwk8rLFtGbR1JuwPMNL5EDw=="],
|
||||||
|
|
||||||
"@unocss/preset-web-fonts": ["@unocss/preset-web-fonts@66.6.2", "", { "dependencies": { "@unocss/core": "66.6.2", "ofetch": "^1.5.1" } }, "sha512-0ckqiE8HkhETeghhxCXVGf96sNPhgBsB5q32iAuMM0HFR4x+ANiLqyfKrm/iqxKUw6rVO4+ItTV0RUWKcZvkXg=="],
|
"@unocss/preset-web-fonts": ["@unocss/preset-web-fonts@66.6.7", "", { "dependencies": { "@unocss/core": "66.6.7", "ofetch": "^1.5.1" } }, "sha512-GLjUoSL/kYt1Yw2zpzixKnxvpHgLHAg0JXiPglct4PZ9YmUzCPbvJ/vVn+0AnB8Fxr29Z8NAFSNoX625ZaRonQ=="],
|
||||||
|
|
||||||
"@unocss/preset-wind": ["@unocss/preset-wind@66.6.2", "", { "dependencies": { "@unocss/core": "66.6.2", "@unocss/preset-wind3": "66.6.2" } }, "sha512-G0H4baUizmTByEowqGuYbKpU2TTisDhZ9W7hrIpYFbRkFv0i1kN2mIxCwj/FLmdY/6x8iSRJ7rO8Nez63YYhnw=="],
|
"@unocss/preset-wind": ["@unocss/preset-wind@66.6.7", "", { "dependencies": { "@unocss/core": "66.6.7", "@unocss/preset-wind3": "66.6.7" } }, "sha512-jxtAN96jljd+KglbhPv6Y/ujceI5rVdrLQimj4KUTPoYBPEiWadzsGKN3o8Q07hlPRg+hBlO0r4tGSUWl+/EZQ=="],
|
||||||
|
|
||||||
"@unocss/preset-wind3": ["@unocss/preset-wind3@66.6.2", "", { "dependencies": { "@unocss/core": "66.6.2", "@unocss/preset-mini": "66.6.2", "@unocss/rule-utils": "66.6.2" } }, "sha512-UqdU2Obx3wXid9xeBHGY1MWxedXa43MGuP5Z2FA9modcXptReux4Zhy764SeQwx6acOUEql2/CTvOBwelZzheQ=="],
|
"@unocss/preset-wind3": ["@unocss/preset-wind3@66.6.7", "", { "dependencies": { "@unocss/core": "66.6.7", "@unocss/preset-mini": "66.6.7", "@unocss/rule-utils": "66.6.7" } }, "sha512-PKyqeRzlIMd3Irdt6fCKMm73zgwweiXESk5edUK8dVWndvPIcZCOqrEq7yg6Pr/Q8tHdq26viYSkVY3a3t8RSg=="],
|
||||||
|
|
||||||
"@unocss/preset-wind4": ["@unocss/preset-wind4@66.6.2", "", { "dependencies": { "@unocss/core": "66.6.2", "@unocss/extractor-arbitrary-variants": "66.6.2", "@unocss/rule-utils": "66.6.2" } }, "sha512-XU+4NN9QIMefawDB9FqOeKONXeGDUJQuQgOeBcpbV/jwOYtyqRrHiqQg++fy1hRbluM+S+KqwRHYjvje8zCTow=="],
|
"@unocss/preset-wind4": ["@unocss/preset-wind4@66.6.7", "", { "dependencies": { "@unocss/core": "66.6.7", "@unocss/extractor-arbitrary-variants": "66.6.7", "@unocss/rule-utils": "66.6.7" } }, "sha512-9grhWeBsFzpv8iER9AFATRaxLyXMCwGQ5HzeI4XZh2ZZ9O6vC7nYfGhns4/I+F/RpFglzU1bjqMWRS/DS8OpGQ=="],
|
||||||
|
|
||||||
"@unocss/rule-utils": ["@unocss/rule-utils@66.6.2", "", { "dependencies": { "@unocss/core": "^66.6.2", "magic-string": "^0.30.21" } }, "sha512-cygfCtkeMrqMM6si1cnyOF16sS7M2gCAqgmZybAhGV7tmH7V8Izn52JZiZIrxVRNMz9dWMVWerHEI9nLbFdbrg=="],
|
"@unocss/rule-utils": ["@unocss/rule-utils@66.6.7", "", { "dependencies": { "@unocss/core": "^66.6.7", "magic-string": "^0.30.21" } }, "sha512-4PT/s8yKIShSqP9XPSw4EjbZopcu3wlIB9i3kbGbzQwF91H+0Yy10guK3kHDGtkmWVN6Np6VvaGIj2UcbmaivA=="],
|
||||||
|
|
||||||
"@unocss/transformer-attributify-jsx": ["@unocss/transformer-attributify-jsx@66.6.2", "", { "dependencies": { "@babel/parser": "7.27.7", "@babel/traverse": "7.27.7", "@unocss/core": "66.6.2" } }, "sha512-WiAEdEowGjQWu1ayhkGGBNGyw3mZLzZ+V5o3zx5U2GPuqvP67YIUfvY+/gTkCnd4+A8unkb+a1VeVgr4cHUkQw=="],
|
"@unocss/transformer-attributify-jsx": ["@unocss/transformer-attributify-jsx@66.6.7", "", { "dependencies": { "@unocss/core": "66.6.7", "oxc-parser": "^0.115.0", "oxc-walker": "^0.7.0" } }, "sha512-r5bsnaPVe4iySLK5G5rA/QPSKmpPjYT9lixEv+KElvZcqZ+cPpkGoo+E+rnTcapu9KDMOVJItH/4Zy9m4AQ1ZQ=="],
|
||||||
|
|
||||||
"@unocss/transformer-compile-class": ["@unocss/transformer-compile-class@66.6.2", "", { "dependencies": { "@unocss/core": "66.6.2" } }, "sha512-L0yaQAmvWkm6LVLXMviqhHIi4c7WQpZFBgJF8jfsALyHihh8K9U9OrRJ81zfLH3Ltw5ZbGzoDE8m/2bB6aRhyw=="],
|
"@unocss/transformer-compile-class": ["@unocss/transformer-compile-class@66.6.7", "", { "dependencies": { "@unocss/core": "66.6.7" } }, "sha512-4uz4jCyq8VUaSPveXhelUWUNaTnetPFvEmXzmbYJ5BygAlUlipNynffUlUusDQmBBRrfZhJNB5J1Zif2Q6oUiA=="],
|
||||||
|
|
||||||
"@unocss/transformer-directives": ["@unocss/transformer-directives@66.6.2", "", { "dependencies": { "@unocss/core": "66.6.2", "@unocss/rule-utils": "66.6.2", "css-tree": "^3.1.0" } }, "sha512-gjLDLItTUJ4CV8K2AA0cw381a7rJ3U4kCHQmZmN3+956o2R7cEHSLyEczmMy04Mg2JBomrjIZjo+L66z5rvblQ=="],
|
"@unocss/transformer-directives": ["@unocss/transformer-directives@66.6.7", "", { "dependencies": { "@unocss/core": "66.6.7", "@unocss/rule-utils": "66.6.7", "css-tree": "^3.1.0" } }, "sha512-z3gi8/cD2P0I+c6jOPZUtsPXknHwVNlMIitSh7LhyM6W3EqbqvDcYH2gFeGhdhoYcN2r5OpTBujq34iz4IdUxA=="],
|
||||||
|
|
||||||
"@unocss/transformer-variant-group": ["@unocss/transformer-variant-group@66.6.2", "", { "dependencies": { "@unocss/core": "66.6.2" } }, "sha512-Uoo6xthOHJ36NdN4b7s/Y7R3fZOf4JYgKzuldHEyHAo0LL204Ss+Ah0+TEt4v72aq+Z86vrLJPyYCeGNKdr8cA=="],
|
"@unocss/transformer-variant-group": ["@unocss/transformer-variant-group@66.6.7", "", { "dependencies": { "@unocss/core": "66.6.7" } }, "sha512-XouJuQCjYJpvR3sY4QDXnGXxtyJ4qgWFG+S9bAB01TTslhQLvNPE9o2+4gZlltnJLqxiPQWuLeJA1KdPD6ciww=="],
|
||||||
|
|
||||||
"@unocss/vite": ["@unocss/vite@66.6.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "@unocss/config": "66.6.2", "@unocss/core": "66.6.2", "@unocss/inspector": "66.6.2", "chokidar": "^5.0.0", "magic-string": "^0.30.21", "pathe": "^2.0.3", "tinyglobby": "^0.2.15", "unplugin-utils": "^0.3.1" }, "peerDependencies": { "vite": "^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 || ^8.0.0-0" } }, "sha512-HLmzDvde3BJ2C6iromHVE21lmNm4SmGSMlbSbFuLPOmWV11XhhHBkAOzytSxPBRG0dbuo+InSGUM14Ek2d6UDg=="],
|
"@unocss/vite": ["@unocss/vite@66.6.7", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "@unocss/config": "66.6.7", "@unocss/core": "66.6.7", "@unocss/inspector": "66.6.7", "chokidar": "^5.0.0", "magic-string": "^0.30.21", "pathe": "^2.0.3", "tinyglobby": "^0.2.15", "unplugin-utils": "^0.3.1" }, "peerDependencies": { "vite": "^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 || ^8.0.0-0" } }, "sha512-8AHrVzAecnQaPLJv3/mpyFt5j2iL3gEwkZcZ8HzjH5ttK2XON1YE9vgujN5NS/yvZwlJxCMNPxn0S410/Ek61A=="],
|
||||||
|
|
||||||
"@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.4", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.2" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", "vue": "^3.2.25" } }, "sha512-uM5iXipgYIn13UUQCZNdWkYk+sysBeA97d5mHsAoAt1u/wpN3+zxOmsVJWosuzX+IMGRzeYUNytztrYznboIkQ=="],
|
"@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.5", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.2" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", "vue": "^3.2.25" } }, "sha512-bL3AxKuQySfk1iGcBsQnoRVexTPJq0Z/ixFVM8OhVJAP6ZXXXLtM7NFKWhLl30Kg7uTBqIaPXbh+nuQCuBDedg=="],
|
||||||
|
|
||||||
"@vitejs/plugin-vue-jsx": ["@vitejs/plugin-vue-jsx@5.1.4", "", { "dependencies": { "@babel/core": "^7.29.0", "@babel/plugin-syntax-typescript": "^7.28.6", "@babel/plugin-transform-typescript": "^7.28.6", "@rolldown/pluginutils": "^1.0.0-rc.2", "@vue/babel-plugin-jsx": "^2.0.1" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", "vue": "^3.0.0" } }, "sha512-70LmoVk9riR7qc4W2CpjsbNMWTPnuZb9dpFKX1emru0yP57nsc9k8nhLA6U93ngQapv5VDIUq2JatNfLbBIkrA=="],
|
"@vitejs/plugin-vue-jsx": ["@vitejs/plugin-vue-jsx@5.1.5", "", { "dependencies": { "@babel/core": "^7.29.0", "@babel/plugin-syntax-typescript": "^7.28.6", "@babel/plugin-transform-typescript": "^7.28.6", "@rolldown/pluginutils": "^1.0.0-rc.2", "@vue/babel-plugin-jsx": "^2.0.1" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", "vue": "^3.0.0" } }, "sha512-jIAsvHOEtWpslLOI2MeElGFxH7M8pM83BU/Tor4RLyiwH0FM4nUW3xdvbw20EeU9wc5IspQwMq225K3CMnJEpA=="],
|
||||||
|
|
||||||
"@vue-macros/common": ["@vue-macros/common@3.1.2", "", { "dependencies": { "@vue/compiler-sfc": "^3.5.22", "ast-kit": "^2.1.2", "local-pkg": "^1.1.2", "magic-string-ast": "^1.0.2", "unplugin-utils": "^0.3.0" }, "peerDependencies": { "vue": "^2.7.0 || ^3.2.25" }, "optionalPeers": ["vue"] }, "sha512-h9t4ArDdniO9ekYHAD95t9AZcAbb19lEGK+26iAjUODOIJKmObDNBSe4+6ELQAA3vtYiFPPBtHh7+cQCKi3Dng=="],
|
"@vue-macros/common": ["@vue-macros/common@3.1.2", "", { "dependencies": { "@vue/compiler-sfc": "^3.5.22", "ast-kit": "^2.1.2", "local-pkg": "^1.1.2", "magic-string-ast": "^1.0.2", "unplugin-utils": "^0.3.0" }, "peerDependencies": { "vue": "^2.7.0 || ^3.2.25" }, "optionalPeers": ["vue"] }, "sha512-h9t4ArDdniO9ekYHAD95t9AZcAbb19lEGK+26iAjUODOIJKmObDNBSe4+6ELQAA3vtYiFPPBtHh7+cQCKi3Dng=="],
|
||||||
|
|
||||||
@@ -353,13 +322,13 @@
|
|||||||
|
|
||||||
"@vue/babel-plugin-resolve-type": ["@vue/babel-plugin-resolve-type@2.0.1", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/helper-module-imports": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/parser": "^7.28.4", "@vue/compiler-sfc": "^3.5.22" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ybwgIuRGRRBhOU37GImDoWQoz+TlSqap65qVI6iwg/J7FfLTLmMf97TS7xQH9I7Qtr/gp161kYVdhr1ZMraSYQ=="],
|
"@vue/babel-plugin-resolve-type": ["@vue/babel-plugin-resolve-type@2.0.1", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/helper-module-imports": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/parser": "^7.28.4", "@vue/compiler-sfc": "^3.5.22" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ybwgIuRGRRBhOU37GImDoWQoz+TlSqap65qVI6iwg/J7FfLTLmMf97TS7xQH9I7Qtr/gp161kYVdhr1ZMraSYQ=="],
|
||||||
|
|
||||||
"@vue/compiler-core": ["@vue/compiler-core@3.5.29", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/shared": "3.5.29", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-cuzPhD8fwRHk8IGfmYaR4eEe4cAyJEL66Ove/WZL7yWNL134nqLddSLwNRIsFlnnW1kK+p8Ck3viFnC0chXCXw=="],
|
"@vue/compiler-core": ["@vue/compiler-core@3.5.31", "", { "dependencies": { "@babel/parser": "^7.29.2", "@vue/shared": "3.5.31", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-k/ueL14aNIEy5Onf0OVzR8kiqF/WThgLdFhxwa4e/KF/0qe38IwIdofoSWBTvvxQOesaz6riAFAUaYjoF9fLLQ=="],
|
||||||
|
|
||||||
"@vue/compiler-dom": ["@vue/compiler-dom@3.5.29", "", { "dependencies": { "@vue/compiler-core": "3.5.29", "@vue/shared": "3.5.29" } }, "sha512-n0G5o7R3uBVmVxjTIYcz7ovr8sy7QObFG8OQJ3xGCDNhbG60biP/P5KnyY8NLd81OuT1WJflG7N4KWYHaeeaIg=="],
|
"@vue/compiler-dom": ["@vue/compiler-dom@3.5.31", "", { "dependencies": { "@vue/compiler-core": "3.5.31", "@vue/shared": "3.5.31" } }, "sha512-BMY/ozS/xxjYqRFL+tKdRpATJYDTTgWSo0+AJvJNg4ig+Hgb0dOsHPXvloHQ5hmlivUqw1Yt2pPIqp4e0v1GUw=="],
|
||||||
|
|
||||||
"@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.29", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/compiler-core": "3.5.29", "@vue/compiler-dom": "3.5.29", "@vue/compiler-ssr": "3.5.29", "@vue/shared": "3.5.29", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.6", "source-map-js": "^1.2.1" } }, "sha512-oJZhN5XJs35Gzr50E82jg2cYdZQ78wEwvRO6Y63TvLVTc+6xICzJHP1UIecdSPPYIbkautNBanDiWYa64QSFIA=="],
|
"@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.31", "", { "dependencies": { "@babel/parser": "^7.29.2", "@vue/compiler-core": "3.5.31", "@vue/compiler-dom": "3.5.31", "@vue/compiler-ssr": "3.5.31", "@vue/shared": "3.5.31", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.8", "source-map-js": "^1.2.1" } }, "sha512-M8wpPgR9UJ8MiRGjppvx9uWJfLV7A/T+/rL8s/y3QG3u0c2/YZgff3d6SuimKRIhcYnWg5fTfDMlz2E6seUW8Q=="],
|
||||||
|
|
||||||
"@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.29", "", { "dependencies": { "@vue/compiler-dom": "3.5.29", "@vue/shared": "3.5.29" } }, "sha512-Y/ARJZE6fpjzL5GH/phJmsFwx3g6t2KmHKHx5q+MLl2kencADKIrhH5MLF6HHpRMmlRAYBRSvv347Mepf1zVNw=="],
|
"@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.31", "", { "dependencies": { "@vue/compiler-dom": "3.5.31", "@vue/shared": "3.5.31" } }, "sha512-h0xIMxrt/LHOvJKMri+vdYT92BrK3HFLtDqq9Pr/lVVfE4IyKZKvWf0vJFW10Yr6nX02OR4MkJwI0c1HDa1hog=="],
|
||||||
|
|
||||||
"@vue/devtools-api": ["@vue/devtools-api@7.7.9", "", { "dependencies": { "@vue/devtools-kit": "^7.7.9" } }, "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g=="],
|
"@vue/devtools-api": ["@vue/devtools-api@7.7.9", "", { "dependencies": { "@vue/devtools-kit": "^7.7.9" } }, "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g=="],
|
||||||
|
|
||||||
@@ -367,15 +336,15 @@
|
|||||||
|
|
||||||
"@vue/devtools-shared": ["@vue/devtools-shared@7.7.9", "", { "dependencies": { "rfdc": "^1.4.1" } }, "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA=="],
|
"@vue/devtools-shared": ["@vue/devtools-shared@7.7.9", "", { "dependencies": { "rfdc": "^1.4.1" } }, "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA=="],
|
||||||
|
|
||||||
"@vue/reactivity": ["@vue/reactivity@3.5.29", "", { "dependencies": { "@vue/shared": "3.5.29" } }, "sha512-zcrANcrRdcLtmGZETBxWqIkoQei8HaFpZWx/GHKxx79JZsiZ8j1du0VUJtu4eJjgFvU/iKL5lRXFXksVmI+5DA=="],
|
"@vue/reactivity": ["@vue/reactivity@3.5.31", "", { "dependencies": { "@vue/shared": "3.5.31" } }, "sha512-DtKXxk9E/KuVvt8VxWu+6Luc9I9ETNcqR1T1oW1gf02nXaZ1kuAx58oVu7uX9XxJR0iJCro6fqBLw9oSBELo5g=="],
|
||||||
|
|
||||||
"@vue/runtime-core": ["@vue/runtime-core@3.5.29", "", { "dependencies": { "@vue/reactivity": "3.5.29", "@vue/shared": "3.5.29" } }, "sha512-8DpW2QfdwIWOLqtsNcds4s+QgwSaHSJY/SUe04LptianUQ/0xi6KVsu/pYVh+HO3NTVvVJjIPL2t6GdeKbS4Lg=="],
|
"@vue/runtime-core": ["@vue/runtime-core@3.5.31", "", { "dependencies": { "@vue/reactivity": "3.5.31", "@vue/shared": "3.5.31" } }, "sha512-AZPmIHXEAyhpkmN7aWlqjSfYynmkWlluDNPHMCZKFHH+lLtxP/30UJmoVhXmbDoP1Ng0jG0fyY2zCj1PnSSA6Q=="],
|
||||||
|
|
||||||
"@vue/runtime-dom": ["@vue/runtime-dom@3.5.29", "", { "dependencies": { "@vue/reactivity": "3.5.29", "@vue/runtime-core": "3.5.29", "@vue/shared": "3.5.29", "csstype": "^3.2.3" } }, "sha512-AHvvJEtcY9tw/uk+s/YRLSlxxQnqnAkjqvK25ZiM4CllCZWzElRAoQnCM42m9AHRLNJ6oe2kC5DCgD4AUdlvXg=="],
|
"@vue/runtime-dom": ["@vue/runtime-dom@3.5.31", "", { "dependencies": { "@vue/reactivity": "3.5.31", "@vue/runtime-core": "3.5.31", "@vue/shared": "3.5.31", "csstype": "^3.2.3" } }, "sha512-xQJsNRmGPeDCJq/u813tyonNgWBFjzfVkBwDREdEWndBnGdHLHgkwNBQxLtg4zDrzKTEcnikUy1UUNecb3lJ6g=="],
|
||||||
|
|
||||||
"@vue/server-renderer": ["@vue/server-renderer@3.5.29", "", { "dependencies": { "@vue/compiler-ssr": "3.5.29", "@vue/shared": "3.5.29" }, "peerDependencies": { "vue": "3.5.29" } }, "sha512-G/1k6WK5MusLlbxSE2YTcqAAezS+VuwHhOvLx2KnQU7G2zCH6KIb+5Wyt6UjMq7a3qPzNEjJXs1hvAxDclQH+g=="],
|
"@vue/server-renderer": ["@vue/server-renderer@3.5.31", "", { "dependencies": { "@vue/compiler-ssr": "3.5.31", "@vue/shared": "3.5.31" }, "peerDependencies": { "vue": "3.5.31" } }, "sha512-GJuwRvMcdZX/CriUnyIIOGkx3rMV3H6sOu0JhdKbduaeCji6zb60iOGMY7tFoN24NfsUYoFBhshZtGxGpxO4iA=="],
|
||||||
|
|
||||||
"@vue/shared": ["@vue/shared@3.5.29", "", {}, "sha512-w7SR0A5zyRByL9XUkCfdLs7t9XOHUyJ67qPGQjOou3p6GvBeBW+AVjUUmlxtZ4PIYaRvE+1LmK44O4uajlZwcg=="],
|
"@vue/shared": ["@vue/shared@3.5.31", "", {}, "sha512-nBxuiuS9Lj5bPkPbWogPUnjxxWpkRniX7e5UBQDWl6Fsf4roq9wwV+cR7ezQ4zXswNvPIlsdj1slcLB7XCsRAw=="],
|
||||||
|
|
||||||
"@vueuse/core": ["@vueuse/core@14.2.1", "", { "dependencies": { "@types/web-bluetooth": "^0.0.21", "@vueuse/metadata": "14.2.1", "@vueuse/shared": "14.2.1" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-3vwDzV+GDUNpdegRY6kzpLm4Igptq+GA0QkJ3W61Iv27YWwW/ufSlOfgQIpN6FZRMG0mkaz4gglJRtq5SeJyIQ=="],
|
"@vueuse/core": ["@vueuse/core@14.2.1", "", { "dependencies": { "@types/web-bluetooth": "^0.0.21", "@vueuse/metadata": "14.2.1", "@vueuse/shared": "14.2.1" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-3vwDzV+GDUNpdegRY6kzpLm4Igptq+GA0QkJ3W61Iv27YWwW/ufSlOfgQIpN6FZRMG0mkaz4gglJRtq5SeJyIQ=="],
|
||||||
|
|
||||||
@@ -383,30 +352,48 @@
|
|||||||
|
|
||||||
"@vueuse/shared": ["@vueuse/shared@14.2.1", "", { "peerDependencies": { "vue": "^3.5.0" } }, "sha512-shTJncjV9JTI4oVNyF1FQonetYAiTBd+Qj7cY89SWbXSkx7gyhrgtEdF2ZAVWS1S3SHlaROO6F2IesJxQEkZBw=="],
|
"@vueuse/shared": ["@vueuse/shared@14.2.1", "", { "peerDependencies": { "vue": "^3.5.0" } }, "sha512-shTJncjV9JTI4oVNyF1FQonetYAiTBd+Qj7cY89SWbXSkx7gyhrgtEdF2ZAVWS1S3SHlaROO6F2IesJxQEkZBw=="],
|
||||||
|
|
||||||
|
"@whatwg-node/fetch": ["@whatwg-node/fetch@0.9.23", "", { "dependencies": { "@whatwg-node/node-fetch": "^0.6.0", "urlpattern-polyfill": "^10.0.0" } }, "sha512-7xlqWel9JsmxahJnYVUj/LLxWcnA93DR4c9xlw3U814jWTiYalryiH1qToik1hOxweKKRLi4haXHM5ycRksPBA=="],
|
||||||
|
|
||||||
|
"@whatwg-node/node-fetch": ["@whatwg-node/node-fetch@0.6.0", "", { "dependencies": { "@kamilkisiela/fast-url-parser": "^1.1.4", "busboy": "^1.6.0", "fast-querystring": "^1.1.1", "tslib": "^2.6.3" } }, "sha512-tcZAhrpx6oVlkEsRngeTEEE7I5/QdLjeEz4IlekabGaESP7+Dkm/6a9KcF1KdCBB7mO9PXtBkwCuTCt8+UPg8Q=="],
|
||||||
|
|
||||||
"acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
|
"acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
|
||||||
|
|
||||||
|
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||||
|
|
||||||
|
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||||
|
|
||||||
"ast-kit": ["ast-kit@2.2.0", "", { "dependencies": { "@babel/parser": "^7.28.5", "pathe": "^2.0.3" } }, "sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw=="],
|
"ast-kit": ["ast-kit@2.2.0", "", { "dependencies": { "@babel/parser": "^7.28.5", "pathe": "^2.0.3" } }, "sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw=="],
|
||||||
|
|
||||||
"ast-walker-scope": ["ast-walker-scope@0.8.3", "", { "dependencies": { "@babel/parser": "^7.28.4", "ast-kit": "^2.1.3" } }, "sha512-cbdCP0PGOBq0ASG+sjnKIoYkWMKhhz+F/h9pRexUdX2Hd38+WOlBkRKlqkGOSm0YQpcFMQBJeK4WspUAkwsEdg=="],
|
"ast-walker-scope": ["ast-walker-scope@0.8.3", "", { "dependencies": { "@babel/parser": "^7.28.4", "ast-kit": "^2.1.3" } }, "sha512-cbdCP0PGOBq0ASG+sjnKIoYkWMKhhz+F/h9pRexUdX2Hd38+WOlBkRKlqkGOSm0YQpcFMQBJeK4WspUAkwsEdg=="],
|
||||||
|
|
||||||
"aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="],
|
"aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="],
|
||||||
|
|
||||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.0", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA=="],
|
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.10", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-sUoJ3IMxx4AyRqO4MLeHlnGDkyXRoUG0/AI9fjK+vS72ekpV0yWVY7O0BVjmBcRtkNcsAO2QDZ4tdKKGoI6YaQ=="],
|
||||||
|
|
||||||
"birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="],
|
"birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="],
|
||||||
|
|
||||||
"blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="],
|
|
||||||
|
|
||||||
"browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="],
|
"browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="],
|
||||||
|
|
||||||
|
"bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="],
|
||||||
|
|
||||||
|
"busboy": ["busboy@1.6.0", "", { "dependencies": { "streamsearch": "^1.1.0" } }, "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA=="],
|
||||||
|
|
||||||
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
|
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
|
||||||
|
|
||||||
"caniuse-lite": ["caniuse-lite@1.0.30001774", "", {}, "sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA=="],
|
"caniuse-lite": ["caniuse-lite@1.0.30001781", "", {}, "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw=="],
|
||||||
|
|
||||||
"chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
|
"chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
|
||||||
|
|
||||||
|
"class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="],
|
||||||
|
|
||||||
|
"cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
|
||||||
|
|
||||||
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
||||||
|
|
||||||
|
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||||
|
|
||||||
|
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||||
|
|
||||||
"colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="],
|
"colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="],
|
||||||
|
|
||||||
"confbox": ["confbox@0.2.4", "", {}, "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ=="],
|
"confbox": ["confbox@0.2.4", "", {}, "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ=="],
|
||||||
@@ -415,11 +402,11 @@
|
|||||||
|
|
||||||
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
||||||
|
|
||||||
"cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
|
|
||||||
|
|
||||||
"copy-anything": ["copy-anything@4.0.5", "", { "dependencies": { "is-what": "^5.2.0" } }, "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA=="],
|
"copy-anything": ["copy-anything@4.0.5", "", { "dependencies": { "is-what": "^5.2.0" } }, "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA=="],
|
||||||
|
|
||||||
"css-tree": ["css-tree@3.1.0", "", { "dependencies": { "mdn-data": "2.12.2", "source-map-js": "^1.0.1" } }, "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w=="],
|
"cross-fetch": ["cross-fetch@4.1.0", "", { "dependencies": { "node-fetch": "^2.7.0" } }, "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw=="],
|
||||||
|
|
||||||
|
"css-tree": ["css-tree@3.2.1", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="],
|
||||||
|
|
||||||
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
||||||
|
|
||||||
@@ -433,14 +420,12 @@
|
|||||||
|
|
||||||
"duplexer": ["duplexer@0.1.2", "", {}, "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg=="],
|
"duplexer": ["duplexer@0.1.2", "", {}, "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg=="],
|
||||||
|
|
||||||
"electron-to-chromium": ["electron-to-chromium@1.5.302", "", {}, "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg=="],
|
"electron-to-chromium": ["electron-to-chromium@1.5.323", "", {}, "sha512-oQm+FxbazvN2WICCbvJgj3IYPKV8awip57+W5VP+Aatk4kFU4pDYCPHZOX22Z27zpw8uttBehEqgK+VTJAYrVw=="],
|
||||||
|
|
||||||
|
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||||
|
|
||||||
"entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="],
|
"entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="],
|
||||||
|
|
||||||
"error-stack-parser-es": ["error-stack-parser-es@1.0.5", "", {}, "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA=="],
|
|
||||||
|
|
||||||
"esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="],
|
|
||||||
|
|
||||||
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||||
|
|
||||||
"escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
|
"escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
|
||||||
@@ -449,19 +434,31 @@
|
|||||||
|
|
||||||
"exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="],
|
"exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="],
|
||||||
|
|
||||||
|
"fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="],
|
||||||
|
|
||||||
|
"fast-querystring": ["fast-querystring@1.1.2", "", { "dependencies": { "fast-decode-uri-component": "^1.0.1" } }, "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg=="],
|
||||||
|
|
||||||
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||||
|
|
||||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||||
|
|
||||||
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
|
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
|
||||||
|
|
||||||
"globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
|
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
|
||||||
|
|
||||||
"gzip-size": ["gzip-size@6.0.0", "", { "dependencies": { "duplexer": "^0.1.2" } }, "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q=="],
|
"gzip-size": ["gzip-size@6.0.0", "", { "dependencies": { "duplexer": "^0.1.2" } }, "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q=="],
|
||||||
|
|
||||||
"hono": ["hono@4.12.2", "", {}, "sha512-gJnaDHXKDayjt8ue0n8Gs0A007yKXj4Xzb8+cNjZeYsSzzwKc0Lr+OZgYwVfB0pHfUs17EPoLvrOsEaJ9mj+Tg=="],
|
"hono": ["hono@4.12.9", "", {}, "sha512-wy3T8Zm2bsEvxKZM5w21VdHDDcwVS1yUFFY6i8UobSsKfFceT7TOwhbhfKsDyx7tYQlmRM5FLpIuYvNFyjctiA=="],
|
||||||
|
|
||||||
"hookable": ["hookable@6.0.1", "", {}, "sha512-uKGyY8BuzN/a5gvzvA+3FVWo0+wUjgtfSdnmjtrOVwQCZPHpHDH2WRO3VZSOeluYrHoDCiXFffZXs8Dj1ULWtw=="],
|
"hookable": ["hookable@6.1.0", "", {}, "sha512-ZoKZSJgu8voGK2geJS+6YtYjvIzu9AOM/KZXsBxr83uhLL++e9pEv/dlgwgy3dvHg06kTz6JOh1hk3C8Ceiymw=="],
|
||||||
|
|
||||||
|
"i18next": ["i18next@26.0.3", "", { "dependencies": { "@babel/runtime": "^7.29.2" }, "peerDependencies": { "typescript": "^5 || ^6" }, "optionalPeers": ["typescript"] }, "sha512-1571kXINxHKY7LksWp8wP+zP0YqHSSpl/OW0Y0owFEf2H3s8gCAffWaZivcz14rMkOvn3R/psiQxVsR9t2Nafg=="],
|
||||||
|
|
||||||
|
"i18next-http-backend": ["i18next-http-backend@3.0.4", "", { "dependencies": { "cross-fetch": "4.1.0" } }, "sha512-udwrBIE6cNpqn1gRAqRULq3+7MzIIuaiKRWrz++dVz5SqWW2VwXmPJtAgkI0JtMLFaADC9qNmnZAxWAhsxXx2g=="],
|
||||||
|
|
||||||
|
"i18next-vue": ["i18next-vue@5.4.0", "", { "peerDependencies": { "i18next": ">=23", "vue": "^3.4.38" } }, "sha512-GDj0Xvmis5Xgcvo9gMBJMgJCtewYMLZP6gAEPDDGCMjA+QeB4uS4qUf1MK79mkz/FukhaJdC+nlj0y1qk6NO2Q=="],
|
||||||
|
|
||||||
|
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
||||||
|
|
||||||
"is-mobile": ["is-mobile@5.0.0", "", {}, "sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ=="],
|
"is-mobile": ["is-mobile@5.0.0", "", {}, "sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ=="],
|
||||||
|
|
||||||
@@ -475,23 +472,53 @@
|
|||||||
|
|
||||||
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
||||||
|
|
||||||
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
|
"lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
|
||||||
|
|
||||||
|
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="],
|
||||||
|
|
||||||
|
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="],
|
||||||
|
|
||||||
|
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="],
|
||||||
|
|
||||||
|
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="],
|
||||||
|
|
||||||
|
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="],
|
||||||
|
|
||||||
|
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="],
|
||||||
|
|
||||||
|
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="],
|
||||||
|
|
||||||
|
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="],
|
||||||
|
|
||||||
|
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="],
|
||||||
|
|
||||||
|
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="],
|
||||||
|
|
||||||
|
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
|
||||||
|
|
||||||
"local-pkg": ["local-pkg@1.1.2", "", { "dependencies": { "mlly": "^1.7.4", "pkg-types": "^2.3.0", "quansync": "^0.2.11" } }, "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A=="],
|
"local-pkg": ["local-pkg@1.1.2", "", { "dependencies": { "mlly": "^1.7.4", "pkg-types": "^2.3.0", "quansync": "^0.2.11" } }, "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A=="],
|
||||||
|
|
||||||
|
"lodash.camelcase": ["lodash.camelcase@4.3.0", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="],
|
||||||
|
|
||||||
|
"long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="],
|
||||||
|
|
||||||
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||||
|
|
||||||
|
"magic-regexp": ["magic-regexp@0.10.0", "", { "dependencies": { "estree-walker": "^3.0.3", "magic-string": "^0.30.12", "mlly": "^1.7.2", "regexp-tree": "^0.1.27", "type-level-regexp": "~0.1.17", "ufo": "^1.5.4", "unplugin": "^2.0.0" } }, "sha512-Uly1Bu4lO1hwHUW0CQeSWuRtzCMNO00CmXtS8N6fyvB3B979GOEEeAkiTUDsmbYLAbvpUS/Kt5c4ibosAzVyVg=="],
|
||||||
|
|
||||||
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||||
|
|
||||||
"magic-string-ast": ["magic-string-ast@1.0.3", "", { "dependencies": { "magic-string": "^0.30.19" } }, "sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA=="],
|
"magic-string-ast": ["magic-string-ast@1.0.3", "", { "dependencies": { "magic-string": "^0.30.19" } }, "sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA=="],
|
||||||
|
|
||||||
"mdn-data": ["mdn-data@2.12.2", "", {}, "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA=="],
|
"mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="],
|
||||||
|
|
||||||
"miniflare": ["miniflare@4.20260302.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "sharp": "^0.34.5", "undici": "7.18.2", "workerd": "1.20260302.0", "ws": "8.18.0", "youch": "4.1.0-beta.10" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-joGFywlo7HdfHXXGOkc6tDCVkwjEncM0mwEsMOLWcl+vDVJPj9HRV7JtEa0+lCpNOLdYw7mZNHYe12xz9KtJOw=="],
|
"mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||||
|
|
||||||
|
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||||
|
|
||||||
"mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="],
|
"mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="],
|
||||||
|
|
||||||
"mlly": ["mlly@1.8.0", "", { "dependencies": { "acorn": "^8.15.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="],
|
"mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="],
|
||||||
|
|
||||||
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
|
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
|
||||||
|
|
||||||
@@ -501,17 +528,21 @@
|
|||||||
|
|
||||||
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||||
|
|
||||||
|
"node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
|
||||||
|
|
||||||
"node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="],
|
"node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="],
|
||||||
|
|
||||||
"node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="],
|
"node-releases": ["node-releases@2.0.36", "", {}, "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA=="],
|
||||||
|
|
||||||
"obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="],
|
"obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="],
|
||||||
|
|
||||||
"ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="],
|
"ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="],
|
||||||
|
|
||||||
"package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="],
|
"oxc-parser": ["oxc-parser@0.115.0", "", { "dependencies": { "@oxc-project/types": "^0.115.0" }, "optionalDependencies": { "@oxc-parser/binding-android-arm-eabi": "0.115.0", "@oxc-parser/binding-android-arm64": "0.115.0", "@oxc-parser/binding-darwin-arm64": "0.115.0", "@oxc-parser/binding-darwin-x64": "0.115.0", "@oxc-parser/binding-freebsd-x64": "0.115.0", "@oxc-parser/binding-linux-arm-gnueabihf": "0.115.0", "@oxc-parser/binding-linux-arm-musleabihf": "0.115.0", "@oxc-parser/binding-linux-arm64-gnu": "0.115.0", "@oxc-parser/binding-linux-arm64-musl": "0.115.0", "@oxc-parser/binding-linux-ppc64-gnu": "0.115.0", "@oxc-parser/binding-linux-riscv64-gnu": "0.115.0", "@oxc-parser/binding-linux-riscv64-musl": "0.115.0", "@oxc-parser/binding-linux-s390x-gnu": "0.115.0", "@oxc-parser/binding-linux-x64-gnu": "0.115.0", "@oxc-parser/binding-linux-x64-musl": "0.115.0", "@oxc-parser/binding-openharmony-arm64": "0.115.0", "@oxc-parser/binding-wasm32-wasi": "0.115.0", "@oxc-parser/binding-win32-arm64-msvc": "0.115.0", "@oxc-parser/binding-win32-ia32-msvc": "0.115.0", "@oxc-parser/binding-win32-x64-msvc": "0.115.0" } }, "sha512-2w7Xn3CbS/zwzSY82S5WLemrRu3CT57uF7Lx8llrE/2bul6iMTcJE4Rbls7GDNbLn3ttATI68PfOz2Pt3KZ2cQ=="],
|
||||||
|
|
||||||
"path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="],
|
"oxc-walker": ["oxc-walker@0.7.0", "", { "dependencies": { "magic-regexp": "^0.10.0" }, "peerDependencies": { "oxc-parser": ">=0.98.0" } }, "sha512-54B4KUhrzbzc4sKvKwVYm7E2PgeROpGba0/2nlNZMqfDyca+yOor5IMb4WLGBatGDT0nkzYdYuzylg7n3YfB7A=="],
|
||||||
|
|
||||||
|
"package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="],
|
||||||
|
|
||||||
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||||
|
|
||||||
@@ -519,67 +550,77 @@
|
|||||||
|
|
||||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||||
|
|
||||||
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
"picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
|
||||||
|
|
||||||
"pinia": ["pinia@3.0.4", "", { "dependencies": { "@vue/devtools-api": "^7.7.7" }, "peerDependencies": { "typescript": ">=4.5.0", "vue": "^3.5.11" }, "optionalPeers": ["typescript"] }, "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw=="],
|
"pinia": ["pinia@3.0.4", "", { "dependencies": { "@vue/devtools-api": "^7.7.7" }, "peerDependencies": { "typescript": ">=4.5.0", "vue": "^3.5.11" }, "optionalPeers": ["typescript"] }, "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw=="],
|
||||||
|
|
||||||
"pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="],
|
"pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="],
|
||||||
|
|
||||||
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
"postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="],
|
||||||
|
|
||||||
|
"protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="],
|
||||||
|
|
||||||
"quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="],
|
"quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="],
|
||||||
|
|
||||||
"readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
|
"readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
|
||||||
|
|
||||||
|
"regexp-tree": ["regexp-tree@0.1.27", "", { "bin": { "regexp-tree": "bin/regexp-tree" } }, "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA=="],
|
||||||
|
|
||||||
|
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
|
||||||
|
|
||||||
"rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],
|
"rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],
|
||||||
|
|
||||||
"rollup": ["rollup@4.59.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.59.0", "@rollup/rollup-android-arm64": "4.59.0", "@rollup/rollup-darwin-arm64": "4.59.0", "@rollup/rollup-darwin-x64": "4.59.0", "@rollup/rollup-freebsd-arm64": "4.59.0", "@rollup/rollup-freebsd-x64": "4.59.0", "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", "@rollup/rollup-linux-arm-musleabihf": "4.59.0", "@rollup/rollup-linux-arm64-gnu": "4.59.0", "@rollup/rollup-linux-arm64-musl": "4.59.0", "@rollup/rollup-linux-loong64-gnu": "4.59.0", "@rollup/rollup-linux-loong64-musl": "4.59.0", "@rollup/rollup-linux-ppc64-gnu": "4.59.0", "@rollup/rollup-linux-ppc64-musl": "4.59.0", "@rollup/rollup-linux-riscv64-gnu": "4.59.0", "@rollup/rollup-linux-riscv64-musl": "4.59.0", "@rollup/rollup-linux-s390x-gnu": "4.59.0", "@rollup/rollup-linux-x64-gnu": "4.59.0", "@rollup/rollup-linux-x64-musl": "4.59.0", "@rollup/rollup-openbsd-x64": "4.59.0", "@rollup/rollup-openharmony-arm64": "4.59.0", "@rollup/rollup-win32-arm64-msvc": "4.59.0", "@rollup/rollup-win32-ia32-msvc": "4.59.0", "@rollup/rollup-win32-x64-gnu": "4.59.0", "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg=="],
|
"rolldown": ["rolldown@1.0.0-rc.12", "", { "dependencies": { "@oxc-project/types": "=0.122.0", "@rolldown/pluginutils": "1.0.0-rc.12" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.12", "@rolldown/binding-darwin-arm64": "1.0.0-rc.12", "@rolldown/binding-darwin-x64": "1.0.0-rc.12", "@rolldown/binding-freebsd-x64": "1.0.0-rc.12", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.12", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.12", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.12", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.12", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.12", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.12", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.12", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.12", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.12", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.12", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.12" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A=="],
|
||||||
|
|
||||||
"scule": ["scule@1.3.0", "", {}, "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g=="],
|
"scule": ["scule@1.3.0", "", {}, "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g=="],
|
||||||
|
|
||||||
"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||||
|
|
||||||
"sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="],
|
|
||||||
|
|
||||||
"sirv": ["sirv@3.0.2", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g=="],
|
"sirv": ["sirv@3.0.2", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g=="],
|
||||||
|
|
||||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||||
|
|
||||||
"speakingurl": ["speakingurl@14.0.1", "", {}, "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ=="],
|
"speakingurl": ["speakingurl@14.0.1", "", {}, "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ=="],
|
||||||
|
|
||||||
|
"streamsearch": ["streamsearch@1.1.0", "", {}, "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="],
|
||||||
|
|
||||||
|
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||||
|
|
||||||
|
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||||
|
|
||||||
"strip-literal": ["strip-literal@3.1.0", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg=="],
|
"strip-literal": ["strip-literal@3.1.0", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg=="],
|
||||||
|
|
||||||
"superjson": ["superjson@2.2.6", "", { "dependencies": { "copy-anything": "^4" } }, "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA=="],
|
"superjson": ["superjson@2.2.6", "", { "dependencies": { "copy-anything": "^4" } }, "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA=="],
|
||||||
|
|
||||||
"supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="],
|
|
||||||
|
|
||||||
"tailwind-merge": ["tailwind-merge@3.5.0", "", {}, "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A=="],
|
"tailwind-merge": ["tailwind-merge@3.5.0", "", {}, "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A=="],
|
||||||
|
|
||||||
"tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
|
"tinyexec": ["tinyexec@1.0.4", "", {}, "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw=="],
|
||||||
|
|
||||||
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
||||||
|
|
||||||
"totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="],
|
"totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="],
|
||||||
|
|
||||||
|
"tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
|
||||||
|
|
||||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||||
|
|
||||||
|
"tweetnacl": ["tweetnacl@1.0.3", "", {}, "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw=="],
|
||||||
|
|
||||||
|
"type-level-regexp": ["type-level-regexp@0.1.17", "", {}, "sha512-wTk4DH3cxwk196uGLK/E9pE45aLfeKJacKmcEgEOA/q5dnPGNxXt0cfYdFxb57L+sEpf1oJH4Dnx/pnRcku9jg=="],
|
||||||
|
|
||||||
"ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="],
|
"ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="],
|
||||||
|
|
||||||
"unconfig": ["unconfig@7.5.0", "", { "dependencies": { "@quansync/fs": "^1.0.0", "defu": "^6.1.4", "jiti": "^2.6.1", "quansync": "^1.0.0", "unconfig-core": "7.5.0" } }, "sha512-oi8Qy2JV4D3UQ0PsopR28CzdQ3S/5A1zwsUwp/rosSbfhJ5z7b90bIyTwi/F7hCLD4SGcZVjDzd4XoUQcEanvA=="],
|
"unconfig": ["unconfig@7.5.0", "", { "dependencies": { "@quansync/fs": "^1.0.0", "defu": "^6.1.4", "jiti": "^2.6.1", "quansync": "^1.0.0", "unconfig-core": "7.5.0" } }, "sha512-oi8Qy2JV4D3UQ0PsopR28CzdQ3S/5A1zwsUwp/rosSbfhJ5z7b90bIyTwi/F7hCLD4SGcZVjDzd4XoUQcEanvA=="],
|
||||||
|
|
||||||
"unconfig-core": ["unconfig-core@7.5.0", "", { "dependencies": { "@quansync/fs": "^1.0.0", "quansync": "^1.0.0" } }, "sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w=="],
|
"unconfig-core": ["unconfig-core@7.5.0", "", { "dependencies": { "@quansync/fs": "^1.0.0", "quansync": "^1.0.0" } }, "sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w=="],
|
||||||
|
|
||||||
"undici": ["undici@7.18.2", "", {}, "sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw=="],
|
|
||||||
|
|
||||||
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
|
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
|
||||||
|
|
||||||
"unenv": ["unenv@2.0.0-rc.24", "", { "dependencies": { "pathe": "^2.0.3" } }, "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw=="],
|
"unhead": ["unhead@2.1.12", "", { "dependencies": { "hookable": "^6.0.1" } }, "sha512-iTHdWD9ztTunOErtfUFk6Wr11BxvzumcYJ0CzaSCBUOEtg+DUZ9+gnE99i8QkLFT2q1rZD48BYYGXpOZVDLYkA=="],
|
||||||
|
|
||||||
"unhead": ["unhead@2.1.9", "", { "dependencies": { "hookable": "^6.0.1" } }, "sha512-4GvP6YeJQzo9J3g9fFZUJOH6jacUp5JgJ0/zC8eZrt8Dwompg9SuOSfrYbZaEzsfMPgQc4fsEjMoY9WzGPOChg=="],
|
"unimport": ["unimport@5.7.0", "", { "dependencies": { "acorn": "^8.16.0", "escape-string-regexp": "^5.0.0", "estree-walker": "^3.0.3", "local-pkg": "^1.1.2", "magic-string": "^0.30.21", "mlly": "^1.8.0", "pathe": "^2.0.3", "picomatch": "^4.0.3", "pkg-types": "^2.3.0", "scule": "^1.3.0", "strip-literal": "^3.1.0", "tinyglobby": "^0.2.15", "unplugin": "^2.3.11", "unplugin-utils": "^0.3.1" } }, "sha512-njnL6sp8lEA8QQbZrt+52p/g4X0rw3bnGGmUcJnt1jeG8+iiqO779aGz0PirCtydAIVcuTBRlJ52F0u46z309Q=="],
|
||||||
|
|
||||||
"unimport": ["unimport@5.6.0", "", { "dependencies": { "acorn": "^8.15.0", "escape-string-regexp": "^5.0.0", "estree-walker": "^3.0.3", "local-pkg": "^1.1.2", "magic-string": "^0.30.21", "mlly": "^1.8.0", "pathe": "^2.0.3", "picomatch": "^4.0.3", "pkg-types": "^2.3.0", "scule": "^1.3.0", "strip-literal": "^3.1.0", "tinyglobby": "^0.2.15", "unplugin": "^2.3.11", "unplugin-utils": "^0.3.1" } }, "sha512-8rqAmtJV8o60x46kBAJKtHpJDJWkA2xcBqWKPI14MgUb05o1pnpnCnXSxedUXyeq7p8fR5g3pTo2BaswZ9lD9A=="],
|
"unocss": ["unocss@66.6.7", "", { "dependencies": { "@unocss/cli": "66.6.7", "@unocss/core": "66.6.7", "@unocss/preset-attributify": "66.6.7", "@unocss/preset-icons": "66.6.7", "@unocss/preset-mini": "66.6.7", "@unocss/preset-tagify": "66.6.7", "@unocss/preset-typography": "66.6.7", "@unocss/preset-uno": "66.6.7", "@unocss/preset-web-fonts": "66.6.7", "@unocss/preset-wind": "66.6.7", "@unocss/preset-wind3": "66.6.7", "@unocss/preset-wind4": "66.6.7", "@unocss/transformer-attributify-jsx": "66.6.7", "@unocss/transformer-compile-class": "66.6.7", "@unocss/transformer-directives": "66.6.7", "@unocss/transformer-variant-group": "66.6.7", "@unocss/vite": "66.6.7" }, "peerDependencies": { "@unocss/astro": "66.6.7", "@unocss/postcss": "66.6.7", "@unocss/webpack": "66.6.7" }, "optionalPeers": ["@unocss/astro", "@unocss/postcss", "@unocss/webpack"] }, "sha512-TdZ/JnKhrqkknrMvLl0KOwrGzFThEspFIyYiylFYJki2JkMN/5EJIr+vIZEGRX69hFTjTLi6utIpbipueqzNbw=="],
|
||||||
|
|
||||||
"unocss": ["unocss@66.6.2", "", { "dependencies": { "@unocss/cli": "66.6.2", "@unocss/core": "66.6.2", "@unocss/preset-attributify": "66.6.2", "@unocss/preset-icons": "66.6.2", "@unocss/preset-mini": "66.6.2", "@unocss/preset-tagify": "66.6.2", "@unocss/preset-typography": "66.6.2", "@unocss/preset-uno": "66.6.2", "@unocss/preset-web-fonts": "66.6.2", "@unocss/preset-wind": "66.6.2", "@unocss/preset-wind3": "66.6.2", "@unocss/preset-wind4": "66.6.2", "@unocss/transformer-attributify-jsx": "66.6.2", "@unocss/transformer-compile-class": "66.6.2", "@unocss/transformer-directives": "66.6.2", "@unocss/transformer-variant-group": "66.6.2", "@unocss/vite": "66.6.2" }, "peerDependencies": { "@unocss/astro": "66.6.2", "@unocss/postcss": "66.6.2", "@unocss/webpack": "66.6.2" }, "optionalPeers": ["@unocss/astro", "@unocss/postcss", "@unocss/webpack"] }, "sha512-ulkfFBFm++/yTdgDn/clpxtm3GxynZi57F4KETQkMQWRXUI7FwqPKGn0xooscvbtldlX67pkovwj/mzkwExitQ=="],
|
|
||||||
|
|
||||||
"unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="],
|
"unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="],
|
||||||
|
|
||||||
@@ -587,45 +628,49 @@
|
|||||||
|
|
||||||
"unplugin-utils": ["unplugin-utils@0.3.1", "", { "dependencies": { "pathe": "^2.0.3", "picomatch": "^4.0.3" } }, "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog=="],
|
"unplugin-utils": ["unplugin-utils@0.3.1", "", { "dependencies": { "pathe": "^2.0.3", "picomatch": "^4.0.3" } }, "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog=="],
|
||||||
|
|
||||||
"unplugin-vue-components": ["unplugin-vue-components@31.0.0", "", { "dependencies": { "chokidar": "^5.0.0", "local-pkg": "^1.1.2", "magic-string": "^0.30.21", "mlly": "^1.8.0", "obug": "^2.1.1", "picomatch": "^4.0.3", "tinyglobby": "^0.2.15", "unplugin": "^2.3.11", "unplugin-utils": "^0.3.1" }, "peerDependencies": { "@nuxt/kit": "^3.2.2 || ^4.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@nuxt/kit"] }, "sha512-4ULwfTZTLuWJ7+S9P7TrcStYLsSRkk6vy2jt/WTfgUEUb0nW9//xxmrfhyHUEVpZ2UKRRwfRb8Yy15PDbVZf+Q=="],
|
"unplugin-vue-components": ["unplugin-vue-components@32.0.0", "", { "dependencies": { "chokidar": "^5.0.0", "local-pkg": "^1.1.2", "magic-string": "^0.30.21", "mlly": "^1.8.2", "obug": "^2.1.1", "picomatch": "^4.0.3", "tinyglobby": "^0.2.15", "unplugin": "^3.0.0", "unplugin-utils": "^0.3.1" }, "peerDependencies": { "@nuxt/kit": "^3.2.2 || ^4.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@nuxt/kit"] }, "sha512-uLdccgS7mf3pv1bCCP20y/hm+u1eOjAmygVkh+Oa70MPkzgl1eQv1L0CwdHNM3gscO8/GDMGIET98Ja47CBbZg=="],
|
||||||
|
|
||||||
"update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
|
"update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
|
||||||
|
|
||||||
"vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="],
|
"urlpattern-polyfill": ["urlpattern-polyfill@10.1.0", "", {}, "sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw=="],
|
||||||
|
|
||||||
|
"vite": ["vite@8.0.3", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.12", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ=="],
|
||||||
|
|
||||||
"vite-ssr-components": ["vite-ssr-components@0.5.2", "", { "dependencies": { "@babel/parser": "^7.27.2", "@babel/traverse": "^7.27.1", "picomatch": "^4.0.2" } }, "sha512-1a8YThRwyyu1gGjc1Ral9Q4uS+n0D4GydhbkVd9c1SA1YNgXyrOizttped87C1ItEznQzhiCyQjaOcYnXa0zMA=="],
|
"vite-ssr-components": ["vite-ssr-components@0.5.2", "", { "dependencies": { "@babel/parser": "^7.27.2", "@babel/traverse": "^7.27.1", "picomatch": "^4.0.2" } }, "sha512-1a8YThRwyyu1gGjc1Ral9Q4uS+n0D4GydhbkVd9c1SA1YNgXyrOizttped87C1ItEznQzhiCyQjaOcYnXa0zMA=="],
|
||||||
|
|
||||||
"vue": ["vue@3.5.29", "", { "dependencies": { "@vue/compiler-dom": "3.5.29", "@vue/compiler-sfc": "3.5.29", "@vue/runtime-dom": "3.5.29", "@vue/server-renderer": "3.5.29", "@vue/shared": "3.5.29" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA=="],
|
"vue": ["vue@3.5.31", "", { "dependencies": { "@vue/compiler-dom": "3.5.31", "@vue/compiler-sfc": "3.5.31", "@vue/runtime-dom": "3.5.31", "@vue/server-renderer": "3.5.31", "@vue/shared": "3.5.31" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-iV/sU9SzOlmA/0tygSmjkEN6Jbs3nPoIPFhCMLD2STrjgOU8DX7ZtzMhg4ahVwf5Rp9KoFzcXeB1ZrVbLBp5/Q=="],
|
||||||
|
|
||||||
"vue-router": ["vue-router@5.0.3", "", { "dependencies": { "@babel/generator": "^7.28.6", "@vue-macros/common": "^3.1.1", "@vue/devtools-api": "^8.0.6", "ast-walker-scope": "^0.8.3", "chokidar": "^5.0.0", "json5": "^2.2.3", "local-pkg": "^1.1.2", "magic-string": "^0.30.21", "mlly": "^1.8.0", "muggle-string": "^0.4.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "scule": "^1.3.0", "tinyglobby": "^0.2.15", "unplugin": "^3.0.0", "unplugin-utils": "^0.3.1", "yaml": "^2.8.2" }, "peerDependencies": { "@pinia/colada": ">=0.21.2", "@vue/compiler-sfc": "^3.5.17", "pinia": "^3.0.4", "vue": "^3.5.0" }, "optionalPeers": ["@pinia/colada", "@vue/compiler-sfc", "pinia"] }, "sha512-nG1c7aAFac7NYj8Hluo68WyWfc41xkEjaR0ViLHCa3oDvTQ/nIuLJlXJX1NUPw/DXzx/8+OKMng045HHQKQKWw=="],
|
"vue-router": ["vue-router@5.0.4", "", { "dependencies": { "@babel/generator": "^7.28.6", "@vue-macros/common": "^3.1.1", "@vue/devtools-api": "^8.0.6", "ast-walker-scope": "^0.8.3", "chokidar": "^5.0.0", "json5": "^2.2.3", "local-pkg": "^1.1.2", "magic-string": "^0.30.21", "mlly": "^1.8.0", "muggle-string": "^0.4.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "scule": "^1.3.0", "tinyglobby": "^0.2.15", "unplugin": "^3.0.0", "unplugin-utils": "^0.3.1", "yaml": "^2.8.2" }, "peerDependencies": { "@pinia/colada": ">=0.21.2", "@vue/compiler-sfc": "^3.5.17", "pinia": "^3.0.4", "vue": "^3.5.0" }, "optionalPeers": ["@pinia/colada", "@vue/compiler-sfc", "pinia"] }, "sha512-lCqDLCI2+fKVRl2OzXuzdSWmxXFLQRxQbmHugnRpTMyYiT+hNaycV0faqG5FBHDXoYrZ6MQcX87BvbY8mQ20Bg=="],
|
||||||
|
|
||||||
|
"webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
|
||||||
|
|
||||||
"webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="],
|
"webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="],
|
||||||
|
|
||||||
"workerd": ["workerd@1.20260302.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20260302.0", "@cloudflare/workerd-darwin-arm64": "1.20260302.0", "@cloudflare/workerd-linux-64": "1.20260302.0", "@cloudflare/workerd-linux-arm64": "1.20260302.0", "@cloudflare/workerd-windows-64": "1.20260302.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-FhNdC8cenMDllI6bTktFgxP5Bn5ZEnGtofgKipY6pW9jtq708D1DeGI6vGad78KQLBGaDwFy1eThjCoLYgFfog=="],
|
"whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
|
||||||
|
|
||||||
"wrangler": ["wrangler@4.68.1", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.2", "@cloudflare/unenv-preset": "2.14.0", "blake3-wasm": "2.1.5", "esbuild": "0.27.3", "miniflare": "4.20260302.0", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20260302.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20260302.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-G+TI3k/olEGBAVkPtUlhAX/DIbL/190fv3aK+r+45/wPclNEymjxCc35T8QGTDhc2fEMXiw51L5bH9aNsBg+yQ=="],
|
"wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||||
|
|
||||||
"ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="],
|
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
|
||||||
|
|
||||||
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||||
|
|
||||||
"yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="],
|
"yaml": ["yaml@2.8.3", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg=="],
|
||||||
|
|
||||||
"youch": ["youch@4.1.0-beta.10", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@poppinss/dumper": "^0.6.4", "@speed-highlight/core": "^1.2.7", "cookie": "^1.0.2", "youch-core": "^0.3.3" } }, "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ=="],
|
"yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
|
||||||
|
|
||||||
"youch-core": ["youch-core@0.3.3", "", { "dependencies": { "@poppinss/exception": "^1.2.2", "error-stack-parser-es": "^1.0.5" } }, "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA=="],
|
"yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
|
||||||
|
|
||||||
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
||||||
|
|
||||||
"@cspotcode/source-map-support/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="],
|
|
||||||
|
|
||||||
"@quansync/fs/quansync": ["quansync@1.0.0", "", {}, "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA=="],
|
"@quansync/fs/quansync": ["quansync@1.0.0", "", {}, "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA=="],
|
||||||
|
|
||||||
"@unocss/transformer-attributify-jsx/@babel/parser": ["@babel/parser@7.27.7", "", { "dependencies": { "@babel/types": "^7.27.7" }, "bin": "./bin/babel-parser.js" }, "sha512-qnzXzDXdr/po3bOTbTIQZ7+TxNKxpkN5IifVLXS+r7qwynkZfPyjZfE7hCXbo7IoO9TNcSyibgONsf2HauUd3Q=="],
|
"@vitejs/plugin-vue-jsx/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.11", "", {}, "sha512-xQO9vbwBecJRv9EUcQ/y0dzSTJgA7Q6UVN7xp6B81+tBGSLVAK03yJ9NkJaUA7JFD91kbjxRSC/mDnmvXzbHoQ=="],
|
||||||
|
|
||||||
"@unocss/transformer-attributify-jsx/@babel/traverse": ["@babel/traverse@7.27.7", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.27.5", "@babel/parser": "^7.27.7", "@babel/template": "^7.27.2", "@babel/types": "^7.27.7", "debug": "^4.3.1", "globals": "^11.1.0" } }, "sha512-X6ZlfR/O/s5EQ/SnUSLzr+6kGnkg8HXGMzpgsMsrJVcfDtH1vIp6ctCN4eZ1LS5c0+te5Cb6Y514fASjMRJ1nw=="],
|
"@vue-macros/common/@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.30", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/compiler-core": "3.5.30", "@vue/compiler-dom": "3.5.30", "@vue/compiler-ssr": "3.5.30", "@vue/shared": "3.5.30", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.8", "source-map-js": "^1.2.1" } }, "sha512-LqmFPDn89dtU9vI3wHJnwaV6GfTRD87AjWpTWpyrdVOObVtjIuSeZr181z5C4PmVx/V3j2p+0f7edFKGRMpQ5A=="],
|
||||||
|
|
||||||
"@vitejs/plugin-vue-jsx/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.5", "", {}, "sha512-RxlLX/DPoarZ9PtxVrQgZhPoor987YtKQqCo5zkjX+0S0yLJ7Vv515Wk6+xtTL67VONKJKxETWZwuZjss2idYw=="],
|
"@vue/babel-plugin-jsx/@vue/shared": ["@vue/shared@3.5.30", "", {}, "sha512-YXgQ7JjaO18NeK2K9VTbDHaFy62WrObMa6XERNfNOkAhD1F1oDSf3ZJ7K6GqabZ0BvSDHajp8qfS5Sa2I9n8uQ=="],
|
||||||
|
|
||||||
|
"@vue/babel-plugin-resolve-type/@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.30", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/compiler-core": "3.5.30", "@vue/compiler-dom": "3.5.30", "@vue/compiler-ssr": "3.5.30", "@vue/shared": "3.5.30", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.8", "source-map-js": "^1.2.1" } }, "sha512-LqmFPDn89dtU9vI3wHJnwaV6GfTRD87AjWpTWpyrdVOObVtjIuSeZr181z5C4PmVx/V3j2p+0f7edFKGRMpQ5A=="],
|
||||||
|
|
||||||
"@vue/compiler-core/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
"@vue/compiler-core/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||||
|
|
||||||
@@ -637,7 +682,9 @@
|
|||||||
|
|
||||||
"mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
|
"mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
|
||||||
|
|
||||||
"sharp/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
"oxc-parser/@oxc-project/types": ["@oxc-project/types@0.115.0", "", {}, "sha512-4n91DKnebUS4yjUHl2g3/b2T+IUdCfmoZGhmwsovZCDaJSs+QkVAM+0AqqTxHSsHfeiMuueT75cZaZcT/m0pSw=="],
|
||||||
|
|
||||||
|
"rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.12", "", {}, "sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw=="],
|
||||||
|
|
||||||
"strip-literal/js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="],
|
"strip-literal/js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="],
|
||||||
|
|
||||||
@@ -645,17 +692,37 @@
|
|||||||
|
|
||||||
"unconfig-core/quansync": ["quansync@1.0.0", "", {}, "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA=="],
|
"unconfig-core/quansync": ["quansync@1.0.0", "", {}, "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA=="],
|
||||||
|
|
||||||
"vue-router/@vue/devtools-api": ["@vue/devtools-api@8.0.6", "", { "dependencies": { "@vue/devtools-kit": "^8.0.6" } }, "sha512-+lGBI+WTvJmnU2FZqHhEB8J1DXcvNlDeEalz77iYgOdY1jTj1ipSBaKj3sRhYcy+kqA8v/BSuvOz1XJucfQmUA=="],
|
"unplugin-vue-components/unplugin": ["unplugin@3.0.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg=="],
|
||||||
|
|
||||||
|
"vue-router/@vue/devtools-api": ["@vue/devtools-api@8.1.1", "", { "dependencies": { "@vue/devtools-kit": "^8.1.1" } }, "sha512-bsDMJ07b3GN1puVwJb/fyFnj/U2imyswK5UQVLZwVl7O05jDrt6BHxeG5XffmOOdasOj/bOmIjxJvGPxU7pcqw=="],
|
||||||
|
|
||||||
"vue-router/unplugin": ["unplugin@3.0.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg=="],
|
"vue-router/unplugin": ["unplugin@3.0.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg=="],
|
||||||
|
|
||||||
"@unocss/transformer-attributify-jsx/@babel/traverse/@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
|
"@vue-macros/common/@vue/compiler-sfc/@vue/compiler-core": ["@vue/compiler-core@3.5.30", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/shared": "3.5.30", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-s3DfdZkcu/qExZ+td75015ljzHc6vE+30cFMGRPROYjqkroYI5NV2X1yAMX9UeyBNWB9MxCfPcsjpLS11nzkkw=="],
|
||||||
|
|
||||||
|
"@vue-macros/common/@vue/compiler-sfc/@vue/compiler-dom": ["@vue/compiler-dom@3.5.30", "", { "dependencies": { "@vue/compiler-core": "3.5.30", "@vue/shared": "3.5.30" } }, "sha512-eCFYESUEVYHhiMuK4SQTldO3RYxyMR/UQL4KdGD1Yrkfdx4m/HYuZ9jSfPdA+nWJY34VWndiYdW/wZXyiPEB9g=="],
|
||||||
|
|
||||||
|
"@vue-macros/common/@vue/compiler-sfc/@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.30", "", { "dependencies": { "@vue/compiler-dom": "3.5.30", "@vue/shared": "3.5.30" } }, "sha512-NsYK6OMTnx109PSL2IAyf62JP6EUdk4Dmj6AkWcJGBvN0dQoMYtVekAmdqgTtWQgEJo+Okstbf/1p7qZr5H+bA=="],
|
||||||
|
|
||||||
|
"@vue-macros/common/@vue/compiler-sfc/@vue/shared": ["@vue/shared@3.5.30", "", {}, "sha512-YXgQ7JjaO18NeK2K9VTbDHaFy62WrObMa6XERNfNOkAhD1F1oDSf3ZJ7K6GqabZ0BvSDHajp8qfS5Sa2I9n8uQ=="],
|
||||||
|
|
||||||
|
"@vue-macros/common/@vue/compiler-sfc/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||||
|
|
||||||
|
"@vue/babel-plugin-resolve-type/@vue/compiler-sfc/@vue/compiler-core": ["@vue/compiler-core@3.5.30", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/shared": "3.5.30", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-s3DfdZkcu/qExZ+td75015ljzHc6vE+30cFMGRPROYjqkroYI5NV2X1yAMX9UeyBNWB9MxCfPcsjpLS11nzkkw=="],
|
||||||
|
|
||||||
|
"@vue/babel-plugin-resolve-type/@vue/compiler-sfc/@vue/compiler-dom": ["@vue/compiler-dom@3.5.30", "", { "dependencies": { "@vue/compiler-core": "3.5.30", "@vue/shared": "3.5.30" } }, "sha512-eCFYESUEVYHhiMuK4SQTldO3RYxyMR/UQL4KdGD1Yrkfdx4m/HYuZ9jSfPdA+nWJY34VWndiYdW/wZXyiPEB9g=="],
|
||||||
|
|
||||||
|
"@vue/babel-plugin-resolve-type/@vue/compiler-sfc/@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.30", "", { "dependencies": { "@vue/compiler-dom": "3.5.30", "@vue/shared": "3.5.30" } }, "sha512-NsYK6OMTnx109PSL2IAyf62JP6EUdk4Dmj6AkWcJGBvN0dQoMYtVekAmdqgTtWQgEJo+Okstbf/1p7qZr5H+bA=="],
|
||||||
|
|
||||||
|
"@vue/babel-plugin-resolve-type/@vue/compiler-sfc/@vue/shared": ["@vue/shared@3.5.30", "", {}, "sha512-YXgQ7JjaO18NeK2K9VTbDHaFy62WrObMa6XERNfNOkAhD1F1oDSf3ZJ7K6GqabZ0BvSDHajp8qfS5Sa2I9n8uQ=="],
|
||||||
|
|
||||||
|
"@vue/babel-plugin-resolve-type/@vue/compiler-sfc/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||||
|
|
||||||
"mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
|
"mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
|
||||||
|
|
||||||
"vue-router/@vue/devtools-api/@vue/devtools-kit": ["@vue/devtools-kit@8.0.6", "", { "dependencies": { "@vue/devtools-shared": "^8.0.6", "birpc": "^2.6.1", "hookable": "^5.5.3", "mitt": "^3.0.1", "perfect-debounce": "^2.0.0", "speakingurl": "^14.0.1", "superjson": "^2.2.2" } }, "sha512-9zXZPTJW72OteDXeSa5RVML3zWDCRcO5t77aJqSs228mdopYj5AiTpihozbsfFJ0IodfNs7pSgOGO3qfCuxDtw=="],
|
"vue-router/@vue/devtools-api/@vue/devtools-kit": ["@vue/devtools-kit@8.1.1", "", { "dependencies": { "@vue/devtools-shared": "^8.1.1", "birpc": "^2.6.1", "hookable": "^5.5.3", "perfect-debounce": "^2.0.0" } }, "sha512-gVBaBv++i+adg4JpH71k9ppl4soyR7Y2McEqO5YNgv0BI1kMZ7BDX5gnwkZ5COYgiCyhejZG+yGNrBAjj6Coqg=="],
|
||||||
|
|
||||||
"vue-router/@vue/devtools-api/@vue/devtools-kit/@vue/devtools-shared": ["@vue/devtools-shared@8.0.6", "", { "dependencies": { "rfdc": "^1.4.1" } }, "sha512-Pp1JylTqlgMJvxW6MGyfTF8vGvlBSCAvMFaDCYa82Mgw7TT5eE5kkHgDvmOGHWeJE4zIDfCpCxHapsK2LtIAJg=="],
|
"vue-router/@vue/devtools-api/@vue/devtools-kit/@vue/devtools-shared": ["@vue/devtools-shared@8.1.1", "", {}, "sha512-+h4ttmJYl/txpxHKaoZcaKpC+pvckgLzIDiSQlaQ7kKthKh8KuwoLW2D8hPJEnqKzXOvu15UHEoGyngAXCz0EQ=="],
|
||||||
|
|
||||||
"vue-router/@vue/devtools-api/@vue/devtools-kit/hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
|
"vue-router/@vue/devtools-api/@vue/devtools-kit/hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
|
||||||
}
|
}
|
||||||
|
|||||||
62
components.d.ts
vendored
62
components.d.ts
vendored
@@ -17,23 +17,28 @@ declare module 'vue' {
|
|||||||
AdvertisementIcon: typeof import('./src/components/icons/AdvertisementIcon.vue')['default']
|
AdvertisementIcon: typeof import('./src/components/icons/AdvertisementIcon.vue')['default']
|
||||||
AlertTriangle: typeof import('./src/components/icons/AlertTriangle.vue')['default']
|
AlertTriangle: typeof import('./src/components/icons/AlertTriangle.vue')['default']
|
||||||
AlertTriangleIcon: typeof import('./src/components/icons/AlertTriangleIcon.vue')['default']
|
AlertTriangleIcon: typeof import('./src/components/icons/AlertTriangleIcon.vue')['default']
|
||||||
AppButton: typeof import('./src/components/app/AppButton.vue')['default']
|
AppButton: typeof import('./src/components/ui/AppButton.vue')['default']
|
||||||
AppConfirmHost: typeof import('./src/components/app/AppConfirmHost.vue')['default']
|
AppConfirmHost: typeof import('./src/components/ui/AppConfirmHost.vue')['default']
|
||||||
AppDialog: typeof import('./src/components/app/AppDialog.vue')['default']
|
AppDialog: typeof import('./src/components/ui/AppDialog.vue')['default']
|
||||||
AppInput: typeof import('./src/components/app/AppInput.vue')['default']
|
AppInput: typeof import('./src/components/ui/AppInput.vue')['default']
|
||||||
AppProgressBar: typeof import('./src/components/app/AppProgressBar.vue')['default']
|
AppProgressBar: typeof import('./src/components/ui/AppProgressBar.vue')['default']
|
||||||
AppSwitch: typeof import('./src/components/app/AppSwitch.vue')['default']
|
AppSwitch: typeof import('./src/components/ui/AppSwitch.vue')['default']
|
||||||
AppToastHost: typeof import('./src/components/app/AppToastHost.vue')['default']
|
AppToastHost: typeof import('./src/components/ui/AppToastHost.vue')['default']
|
||||||
|
AppTopLoadingBar: typeof import('./src/components/AppTopLoadingBar.vue')['default']
|
||||||
ArrowDownTray: typeof import('./src/components/icons/ArrowDownTray.vue')['default']
|
ArrowDownTray: typeof import('./src/components/icons/ArrowDownTray.vue')['default']
|
||||||
ArrowRightIcon: typeof import('./src/components/icons/ArrowRightIcon.vue')['default']
|
ArrowRightIcon: typeof import('./src/components/icons/ArrowRightIcon.vue')['default']
|
||||||
|
AsyncSelect: typeof import('./src/components/ui/AsyncSelect.vue')['default']
|
||||||
|
BaseTable: typeof import('./src/components/ui/BaseTable.vue')['default']
|
||||||
Bell: typeof import('./src/components/icons/Bell.vue')['default']
|
Bell: typeof import('./src/components/icons/Bell.vue')['default']
|
||||||
BellIcon: typeof import('./src/components/icons/BellIcon.vue')['default']
|
BellDot: typeof import('./src/components/icons/BellDot.vue')['default']
|
||||||
|
BellOff: typeof import('./src/components/icons/BellOff.vue')['default']
|
||||||
Chart: typeof import('./src/components/icons/Chart.vue')['default']
|
Chart: typeof import('./src/components/icons/Chart.vue')['default']
|
||||||
CheckCircleIcon: typeof import('./src/components/icons/CheckCircleIcon.vue')['default']
|
CheckCircleIcon: typeof import('./src/components/icons/CheckCircleIcon.vue')['default']
|
||||||
CheckIcon: typeof import('./src/components/icons/CheckIcon.vue')['default']
|
CheckIcon: typeof import('./src/components/icons/CheckIcon.vue')['default']
|
||||||
CheckMarkIcon: typeof import('./src/components/icons/CheckMarkIcon.vue')['default']
|
CheckMarkIcon: typeof import('./src/components/icons/CheckMarkIcon.vue')['default']
|
||||||
ClientOnly: typeof import('./src/components/ClientOnly.tsx')['default']
|
ClientOnly: typeof import('./src/components/ClientOnly.tsx')['default']
|
||||||
CoinsIcon: typeof import('./src/components/icons/CoinsIcon.vue')['default']
|
CoinsIcon: typeof import('./src/components/icons/CoinsIcon.vue')['default']
|
||||||
|
copy: typeof import('./src/components/icons/UserIcon copy.vue')['default']
|
||||||
Credit: typeof import('./src/components/icons/Credit.vue')['default']
|
Credit: typeof import('./src/components/icons/Credit.vue')['default']
|
||||||
CreditCardIcon: typeof import('./src/components/icons/CreditCardIcon.vue')['default']
|
CreditCardIcon: typeof import('./src/components/icons/CreditCardIcon.vue')['default']
|
||||||
DashboardLayout: typeof import('./src/components/DashboardLayout.vue')['default']
|
DashboardLayout: typeof import('./src/components/DashboardLayout.vue')['default']
|
||||||
@@ -45,30 +50,38 @@ declare module 'vue' {
|
|||||||
GlobalUploadIndicator: typeof import('./src/components/GlobalUploadIndicator.vue')['default']
|
GlobalUploadIndicator: typeof import('./src/components/GlobalUploadIndicator.vue')['default']
|
||||||
Globe: typeof import('./src/components/icons/Globe.vue')['default']
|
Globe: typeof import('./src/components/icons/Globe.vue')['default']
|
||||||
GlobeIcon: typeof import('./src/components/icons/GlobeIcon.vue')['default']
|
GlobeIcon: typeof import('./src/components/icons/GlobeIcon.vue')['default']
|
||||||
|
HardDrive: typeof import('./src/components/icons/hard-drive.vue')['default']
|
||||||
HardDriveUpload: typeof import('./src/components/icons/HardDriveUpload.vue')['default']
|
HardDriveUpload: typeof import('./src/components/icons/HardDriveUpload.vue')['default']
|
||||||
HeartIcon: typeof import('./src/components/icons/HeartIcon.vue')['default']
|
HeartIcon: typeof import('./src/components/icons/HeartIcon.vue')['default']
|
||||||
Home: typeof import('./src/components/icons/Home.vue')['default']
|
Home: typeof import('./src/components/icons/Home.vue')['default']
|
||||||
ImageIcon: typeof import('./src/components/icons/ImageIcon.vue')['default']
|
ImageIcon: typeof import('./src/components/icons/ImageIcon.vue')['default']
|
||||||
|
Inbox: typeof import('./src/components/icons/Inbox.vue')['default']
|
||||||
InfoIcon: typeof import('./src/components/icons/InfoIcon.vue')['default']
|
InfoIcon: typeof import('./src/components/icons/InfoIcon.vue')['default']
|
||||||
|
LanguageIcon: typeof import('./src/components/icons/LanguageIcon.vue')['default']
|
||||||
Layout: typeof import('./src/components/icons/Layout.vue')['default']
|
Layout: typeof import('./src/components/icons/Layout.vue')['default']
|
||||||
LayoutDashboard: typeof import('./src/components/icons/LayoutDashboard.vue')['default']
|
LayoutDashboard: typeof import('./src/components/icons/LayoutDashboard.vue')['default']
|
||||||
LinkIcon: typeof import('./src/components/icons/LinkIcon.vue')['default']
|
LinkIcon: typeof import('./src/components/icons/LinkIcon.vue')['default']
|
||||||
|
ListIcon: typeof import('./src/components/icons/ListIcon.vue')['default']
|
||||||
LockIcon: typeof import('./src/components/icons/LockIcon.vue')['default']
|
LockIcon: typeof import('./src/components/icons/LockIcon.vue')['default']
|
||||||
MailIcon: typeof import('./src/components/icons/MailIcon.vue')['default']
|
MailIcon: typeof import('./src/components/icons/MailIcon.vue')['default']
|
||||||
|
MoneyCheck: typeof import('./src/components/icons/MoneyCheck.vue')['default']
|
||||||
MonitorIcon: typeof import('./src/components/icons/MonitorIcon.vue')['default']
|
MonitorIcon: typeof import('./src/components/icons/MonitorIcon.vue')['default']
|
||||||
NotificationDrawer: typeof import('./src/components/NotificationDrawer.vue')['default']
|
NotificationDrawer: typeof import('./src/components/NotificationDrawer.vue')['default']
|
||||||
|
OfflineOverlay: typeof import('./src/components/OfflineOverlay.vue')['default']
|
||||||
PageHeader: typeof import('./src/components/dashboard/PageHeader.vue')['default']
|
PageHeader: typeof import('./src/components/dashboard/PageHeader.vue')['default']
|
||||||
PanelLeft: typeof import('./src/components/icons/PanelLeft.vue')['default']
|
PanelLeft: typeof import('./src/components/icons/PanelLeft.vue')['default']
|
||||||
PencilIcon: typeof import('./src/components/icons/PencilIcon.vue')['default']
|
PencilIcon: typeof import('./src/components/icons/PencilIcon.vue')['default']
|
||||||
PlayIcon: typeof import('./src/components/icons/PlayIcon.vue')['default']
|
PlayIcon: typeof import('./src/components/icons/PlayIcon.vue')['default']
|
||||||
PlusIcon: typeof import('./src/components/icons/PlusIcon.vue')['default']
|
PlusIcon: typeof import('./src/components/icons/PlusIcon.vue')['default']
|
||||||
PlusSquareIcon: typeof import('./src/components/icons/PlusSquareIcon.vue')['default']
|
PlusSquareIcon: typeof import('./src/components/icons/PlusSquareIcon.vue')['default']
|
||||||
|
PopupAdsRuntime: typeof import('./src/components/PopupAdsRuntime.vue')['default']
|
||||||
RepeatIcon: typeof import('./src/components/icons/RepeatIcon.vue')['default']
|
RepeatIcon: typeof import('./src/components/icons/RepeatIcon.vue')['default']
|
||||||
RootLayout: typeof import('./src/components/RootLayout.vue')['default']
|
RootLayout: typeof import('./src/components/RootLayout.vue')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
SendIcon: typeof import('./src/components/icons/SendIcon.vue')['default']
|
SendIcon: typeof import('./src/components/icons/SendIcon.vue')['default']
|
||||||
SettingsIcon: typeof import('./src/components/icons/SettingsIcon.vue')['default']
|
SettingsIcon: typeof import('./src/components/icons/SettingsIcon.vue')['default']
|
||||||
|
ShieldUser: typeof import('./src/components/icons/shield-user.vue')['default']
|
||||||
SlidersIcon: typeof import('./src/components/icons/SlidersIcon.vue')['default']
|
SlidersIcon: typeof import('./src/components/icons/SlidersIcon.vue')['default']
|
||||||
StatsCard: typeof import('./src/components/dashboard/StatsCard.vue')['default']
|
StatsCard: typeof import('./src/components/dashboard/StatsCard.vue')['default']
|
||||||
TelegramIcon: typeof import('./src/components/icons/TelegramIcon.vue')['default']
|
TelegramIcon: typeof import('./src/components/icons/TelegramIcon.vue')['default']
|
||||||
@@ -77,6 +90,7 @@ declare module 'vue' {
|
|||||||
Upload: typeof import('./src/components/icons/Upload.vue')['default']
|
Upload: typeof import('./src/components/icons/Upload.vue')['default']
|
||||||
UploadIcon: typeof import('./src/components/icons/UploadIcon.vue')['default']
|
UploadIcon: typeof import('./src/components/icons/UploadIcon.vue')['default']
|
||||||
UserIcon: typeof import('./src/components/icons/UserIcon.vue')['default']
|
UserIcon: typeof import('./src/components/icons/UserIcon.vue')['default']
|
||||||
|
'UserIcon copy': typeof import('./src/components/icons/UserIcon copy.vue')['default']
|
||||||
Video: typeof import('./src/components/icons/Video.vue')['default']
|
Video: typeof import('./src/components/icons/Video.vue')['default']
|
||||||
VideoIcon: typeof import('./src/components/icons/VideoIcon.vue')['default']
|
VideoIcon: typeof import('./src/components/icons/VideoIcon.vue')['default']
|
||||||
VideoPlayIcon: typeof import('./src/components/icons/VideoPlayIcon.vue')['default']
|
VideoPlayIcon: typeof import('./src/components/icons/VideoPlayIcon.vue')['default']
|
||||||
@@ -84,6 +98,7 @@ declare module 'vue' {
|
|||||||
VolumeOffIcon: typeof import('./src/components/icons/VolumeOffIcon.vue')['default']
|
VolumeOffIcon: typeof import('./src/components/icons/VolumeOffIcon.vue')['default']
|
||||||
VueHead: typeof import('./src/components/VueHead.tsx')['default']
|
VueHead: typeof import('./src/components/VueHead.tsx')['default']
|
||||||
WifiIcon: typeof import('./src/components/icons/WifiIcon.vue')['default']
|
WifiIcon: typeof import('./src/components/icons/WifiIcon.vue')['default']
|
||||||
|
Windows: typeof import('./src/components/icons/windows.vue')['default']
|
||||||
XCircleIcon: typeof import('./src/components/icons/XCircleIcon.vue')['default']
|
XCircleIcon: typeof import('./src/components/icons/XCircleIcon.vue')['default']
|
||||||
XIcon: typeof import('./src/components/icons/XIcon.vue')['default']
|
XIcon: typeof import('./src/components/icons/XIcon.vue')['default']
|
||||||
}
|
}
|
||||||
@@ -96,23 +111,28 @@ declare global {
|
|||||||
const AdvertisementIcon: typeof import('./src/components/icons/AdvertisementIcon.vue')['default']
|
const AdvertisementIcon: typeof import('./src/components/icons/AdvertisementIcon.vue')['default']
|
||||||
const AlertTriangle: typeof import('./src/components/icons/AlertTriangle.vue')['default']
|
const AlertTriangle: typeof import('./src/components/icons/AlertTriangle.vue')['default']
|
||||||
const AlertTriangleIcon: typeof import('./src/components/icons/AlertTriangleIcon.vue')['default']
|
const AlertTriangleIcon: typeof import('./src/components/icons/AlertTriangleIcon.vue')['default']
|
||||||
const AppButton: typeof import('./src/components/app/AppButton.vue')['default']
|
const AppButton: typeof import('./src/components/ui/AppButton.vue')['default']
|
||||||
const AppConfirmHost: typeof import('./src/components/app/AppConfirmHost.vue')['default']
|
const AppConfirmHost: typeof import('./src/components/ui/AppConfirmHost.vue')['default']
|
||||||
const AppDialog: typeof import('./src/components/app/AppDialog.vue')['default']
|
const AppDialog: typeof import('./src/components/ui/AppDialog.vue')['default']
|
||||||
const AppInput: typeof import('./src/components/app/AppInput.vue')['default']
|
const AppInput: typeof import('./src/components/ui/AppInput.vue')['default']
|
||||||
const AppProgressBar: typeof import('./src/components/app/AppProgressBar.vue')['default']
|
const AppProgressBar: typeof import('./src/components/ui/AppProgressBar.vue')['default']
|
||||||
const AppSwitch: typeof import('./src/components/app/AppSwitch.vue')['default']
|
const AppSwitch: typeof import('./src/components/ui/AppSwitch.vue')['default']
|
||||||
const AppToastHost: typeof import('./src/components/app/AppToastHost.vue')['default']
|
const AppToastHost: typeof import('./src/components/ui/AppToastHost.vue')['default']
|
||||||
|
const AppTopLoadingBar: typeof import('./src/components/AppTopLoadingBar.vue')['default']
|
||||||
const ArrowDownTray: typeof import('./src/components/icons/ArrowDownTray.vue')['default']
|
const ArrowDownTray: typeof import('./src/components/icons/ArrowDownTray.vue')['default']
|
||||||
const ArrowRightIcon: typeof import('./src/components/icons/ArrowRightIcon.vue')['default']
|
const ArrowRightIcon: typeof import('./src/components/icons/ArrowRightIcon.vue')['default']
|
||||||
|
const AsyncSelect: typeof import('./src/components/ui/AsyncSelect.vue')['default']
|
||||||
|
const BaseTable: typeof import('./src/components/ui/BaseTable.vue')['default']
|
||||||
const Bell: typeof import('./src/components/icons/Bell.vue')['default']
|
const Bell: typeof import('./src/components/icons/Bell.vue')['default']
|
||||||
const BellIcon: typeof import('./src/components/icons/BellIcon.vue')['default']
|
const BellDot: typeof import('./src/components/icons/BellDot.vue')['default']
|
||||||
|
const BellOff: typeof import('./src/components/icons/BellOff.vue')['default']
|
||||||
const Chart: typeof import('./src/components/icons/Chart.vue')['default']
|
const Chart: typeof import('./src/components/icons/Chart.vue')['default']
|
||||||
const CheckCircleIcon: typeof import('./src/components/icons/CheckCircleIcon.vue')['default']
|
const CheckCircleIcon: typeof import('./src/components/icons/CheckCircleIcon.vue')['default']
|
||||||
const CheckIcon: typeof import('./src/components/icons/CheckIcon.vue')['default']
|
const CheckIcon: typeof import('./src/components/icons/CheckIcon.vue')['default']
|
||||||
const CheckMarkIcon: typeof import('./src/components/icons/CheckMarkIcon.vue')['default']
|
const CheckMarkIcon: typeof import('./src/components/icons/CheckMarkIcon.vue')['default']
|
||||||
const ClientOnly: typeof import('./src/components/ClientOnly.tsx')['default']
|
const ClientOnly: typeof import('./src/components/ClientOnly.tsx')['default']
|
||||||
const CoinsIcon: typeof import('./src/components/icons/CoinsIcon.vue')['default']
|
const CoinsIcon: typeof import('./src/components/icons/CoinsIcon.vue')['default']
|
||||||
|
const copy: typeof import('./src/components/icons/UserIcon copy.vue')['default']
|
||||||
const Credit: typeof import('./src/components/icons/Credit.vue')['default']
|
const Credit: typeof import('./src/components/icons/Credit.vue')['default']
|
||||||
const CreditCardIcon: typeof import('./src/components/icons/CreditCardIcon.vue')['default']
|
const CreditCardIcon: typeof import('./src/components/icons/CreditCardIcon.vue')['default']
|
||||||
const DashboardLayout: typeof import('./src/components/DashboardLayout.vue')['default']
|
const DashboardLayout: typeof import('./src/components/DashboardLayout.vue')['default']
|
||||||
@@ -124,30 +144,38 @@ declare global {
|
|||||||
const GlobalUploadIndicator: typeof import('./src/components/GlobalUploadIndicator.vue')['default']
|
const GlobalUploadIndicator: typeof import('./src/components/GlobalUploadIndicator.vue')['default']
|
||||||
const Globe: typeof import('./src/components/icons/Globe.vue')['default']
|
const Globe: typeof import('./src/components/icons/Globe.vue')['default']
|
||||||
const GlobeIcon: typeof import('./src/components/icons/GlobeIcon.vue')['default']
|
const GlobeIcon: typeof import('./src/components/icons/GlobeIcon.vue')['default']
|
||||||
|
const HardDrive: typeof import('./src/components/icons/hard-drive.vue')['default']
|
||||||
const HardDriveUpload: typeof import('./src/components/icons/HardDriveUpload.vue')['default']
|
const HardDriveUpload: typeof import('./src/components/icons/HardDriveUpload.vue')['default']
|
||||||
const HeartIcon: typeof import('./src/components/icons/HeartIcon.vue')['default']
|
const HeartIcon: typeof import('./src/components/icons/HeartIcon.vue')['default']
|
||||||
const Home: typeof import('./src/components/icons/Home.vue')['default']
|
const Home: typeof import('./src/components/icons/Home.vue')['default']
|
||||||
const ImageIcon: typeof import('./src/components/icons/ImageIcon.vue')['default']
|
const ImageIcon: typeof import('./src/components/icons/ImageIcon.vue')['default']
|
||||||
|
const Inbox: typeof import('./src/components/icons/Inbox.vue')['default']
|
||||||
const InfoIcon: typeof import('./src/components/icons/InfoIcon.vue')['default']
|
const InfoIcon: typeof import('./src/components/icons/InfoIcon.vue')['default']
|
||||||
|
const LanguageIcon: typeof import('./src/components/icons/LanguageIcon.vue')['default']
|
||||||
const Layout: typeof import('./src/components/icons/Layout.vue')['default']
|
const Layout: typeof import('./src/components/icons/Layout.vue')['default']
|
||||||
const LayoutDashboard: typeof import('./src/components/icons/LayoutDashboard.vue')['default']
|
const LayoutDashboard: typeof import('./src/components/icons/LayoutDashboard.vue')['default']
|
||||||
const LinkIcon: typeof import('./src/components/icons/LinkIcon.vue')['default']
|
const LinkIcon: typeof import('./src/components/icons/LinkIcon.vue')['default']
|
||||||
|
const ListIcon: typeof import('./src/components/icons/ListIcon.vue')['default']
|
||||||
const LockIcon: typeof import('./src/components/icons/LockIcon.vue')['default']
|
const LockIcon: typeof import('./src/components/icons/LockIcon.vue')['default']
|
||||||
const MailIcon: typeof import('./src/components/icons/MailIcon.vue')['default']
|
const MailIcon: typeof import('./src/components/icons/MailIcon.vue')['default']
|
||||||
|
const MoneyCheck: typeof import('./src/components/icons/MoneyCheck.vue')['default']
|
||||||
const MonitorIcon: typeof import('./src/components/icons/MonitorIcon.vue')['default']
|
const MonitorIcon: typeof import('./src/components/icons/MonitorIcon.vue')['default']
|
||||||
const NotificationDrawer: typeof import('./src/components/NotificationDrawer.vue')['default']
|
const NotificationDrawer: typeof import('./src/components/NotificationDrawer.vue')['default']
|
||||||
|
const OfflineOverlay: typeof import('./src/components/OfflineOverlay.vue')['default']
|
||||||
const PageHeader: typeof import('./src/components/dashboard/PageHeader.vue')['default']
|
const PageHeader: typeof import('./src/components/dashboard/PageHeader.vue')['default']
|
||||||
const PanelLeft: typeof import('./src/components/icons/PanelLeft.vue')['default']
|
const PanelLeft: typeof import('./src/components/icons/PanelLeft.vue')['default']
|
||||||
const PencilIcon: typeof import('./src/components/icons/PencilIcon.vue')['default']
|
const PencilIcon: typeof import('./src/components/icons/PencilIcon.vue')['default']
|
||||||
const PlayIcon: typeof import('./src/components/icons/PlayIcon.vue')['default']
|
const PlayIcon: typeof import('./src/components/icons/PlayIcon.vue')['default']
|
||||||
const PlusIcon: typeof import('./src/components/icons/PlusIcon.vue')['default']
|
const PlusIcon: typeof import('./src/components/icons/PlusIcon.vue')['default']
|
||||||
const PlusSquareIcon: typeof import('./src/components/icons/PlusSquareIcon.vue')['default']
|
const PlusSquareIcon: typeof import('./src/components/icons/PlusSquareIcon.vue')['default']
|
||||||
|
const PopupAdsRuntime: typeof import('./src/components/PopupAdsRuntime.vue')['default']
|
||||||
const RepeatIcon: typeof import('./src/components/icons/RepeatIcon.vue')['default']
|
const RepeatIcon: typeof import('./src/components/icons/RepeatIcon.vue')['default']
|
||||||
const RootLayout: typeof import('./src/components/RootLayout.vue')['default']
|
const RootLayout: typeof import('./src/components/RootLayout.vue')['default']
|
||||||
const RouterLink: typeof import('vue-router')['RouterLink']
|
const RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
const RouterView: typeof import('vue-router')['RouterView']
|
const RouterView: typeof import('vue-router')['RouterView']
|
||||||
const SendIcon: typeof import('./src/components/icons/SendIcon.vue')['default']
|
const SendIcon: typeof import('./src/components/icons/SendIcon.vue')['default']
|
||||||
const SettingsIcon: typeof import('./src/components/icons/SettingsIcon.vue')['default']
|
const SettingsIcon: typeof import('./src/components/icons/SettingsIcon.vue')['default']
|
||||||
|
const ShieldUser: typeof import('./src/components/icons/shield-user.vue')['default']
|
||||||
const SlidersIcon: typeof import('./src/components/icons/SlidersIcon.vue')['default']
|
const SlidersIcon: typeof import('./src/components/icons/SlidersIcon.vue')['default']
|
||||||
const StatsCard: typeof import('./src/components/dashboard/StatsCard.vue')['default']
|
const StatsCard: typeof import('./src/components/dashboard/StatsCard.vue')['default']
|
||||||
const TelegramIcon: typeof import('./src/components/icons/TelegramIcon.vue')['default']
|
const TelegramIcon: typeof import('./src/components/icons/TelegramIcon.vue')['default']
|
||||||
@@ -156,6 +184,7 @@ declare global {
|
|||||||
const Upload: typeof import('./src/components/icons/Upload.vue')['default']
|
const Upload: typeof import('./src/components/icons/Upload.vue')['default']
|
||||||
const UploadIcon: typeof import('./src/components/icons/UploadIcon.vue')['default']
|
const UploadIcon: typeof import('./src/components/icons/UploadIcon.vue')['default']
|
||||||
const UserIcon: typeof import('./src/components/icons/UserIcon.vue')['default']
|
const UserIcon: typeof import('./src/components/icons/UserIcon.vue')['default']
|
||||||
|
const 'UserIcon copy': typeof import('./src/components/icons/UserIcon copy.vue')['default']
|
||||||
const Video: typeof import('./src/components/icons/Video.vue')['default']
|
const Video: typeof import('./src/components/icons/Video.vue')['default']
|
||||||
const VideoIcon: typeof import('./src/components/icons/VideoIcon.vue')['default']
|
const VideoIcon: typeof import('./src/components/icons/VideoIcon.vue')['default']
|
||||||
const VideoPlayIcon: typeof import('./src/components/icons/VideoPlayIcon.vue')['default']
|
const VideoPlayIcon: typeof import('./src/components/icons/VideoPlayIcon.vue')['default']
|
||||||
@@ -163,6 +192,7 @@ declare global {
|
|||||||
const VolumeOffIcon: typeof import('./src/components/icons/VolumeOffIcon.vue')['default']
|
const VolumeOffIcon: typeof import('./src/components/icons/VolumeOffIcon.vue')['default']
|
||||||
const VueHead: typeof import('./src/components/VueHead.tsx')['default']
|
const VueHead: typeof import('./src/components/VueHead.tsx')['default']
|
||||||
const WifiIcon: typeof import('./src/components/icons/WifiIcon.vue')['default']
|
const WifiIcon: typeof import('./src/components/icons/WifiIcon.vue')['default']
|
||||||
|
const Windows: typeof import('./src/components/icons/windows.vue')['default']
|
||||||
const XCircleIcon: typeof import('./src/components/icons/XCircleIcon.vue')['default']
|
const XCircleIcon: typeof import('./src/components/icons/XCircleIcon.vue')['default']
|
||||||
const XIcon: typeof import('./src/components/icons/XIcon.vue')['default']
|
const XIcon: typeof import('./src/components/icons/XIcon.vue')['default']
|
||||||
}
|
}
|
||||||
54
package.json
54
package.json
@@ -2,37 +2,47 @@
|
|||||||
"name": "holistream",
|
"name": "holistream",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "bun vite",
|
"dev": "bun x --bun vite",
|
||||||
"build": "bun vite build",
|
"build": "bun x --bun vite build",
|
||||||
"preview": "bun vite preview",
|
"preview": "bun x --bun vite preview"
|
||||||
"deploy": "wrangler deploy",
|
|
||||||
"cf-typegen": "wrangler types --env-interface CloudflareBindings",
|
|
||||||
"tail": "wrangler tail"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@pinia/colada": "^0.21.2",
|
"@bufbuild/protobuf": "^2.11.0",
|
||||||
"@unhead/vue": "^2.1.2",
|
"@grpc/grpc-js": "^1.14.3",
|
||||||
"@vueuse/core": "^14.2.0",
|
"@hattip/adapter-node": "^0.0.49",
|
||||||
|
"@hiogawa/tiny-rpc": "^0.2.3-pre.18",
|
||||||
|
"@hiogawa/utils": "^1.7.0",
|
||||||
|
"@hono/node-server": "^1.19.12",
|
||||||
|
"@hono/zod-validator": "^0.7.6",
|
||||||
|
"@pinia/colada": "^1.1.0",
|
||||||
|
"@tanstack/vue-table": "^8.21.3",
|
||||||
|
"@unhead/vue": "^2.1.12",
|
||||||
|
"@vueuse/core": "^14.2.1",
|
||||||
"aws4fetch": "^1.0.20",
|
"aws4fetch": "^1.0.20",
|
||||||
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"hono": "^4.11.7",
|
"hono": "^4.12.9",
|
||||||
|
"i18next": "^26.0.3",
|
||||||
|
"i18next-http-backend": "^3.0.4",
|
||||||
|
"i18next-vue": "^5.4.0",
|
||||||
"is-mobile": "^5.0.0",
|
"is-mobile": "^5.0.0",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"tailwind-merge": "^3.4.0",
|
"superjson": "^2.2.6",
|
||||||
"vue": "^3.5.27",
|
"tailwind-merge": "^3.5.0",
|
||||||
"vue-router": "^5.0.2",
|
"tweetnacl": "^1.0.3",
|
||||||
|
"vue": "^3.5.31",
|
||||||
|
"vue-router": "^5.0.4",
|
||||||
"zod": "^4.3.6"
|
"zod": "^4.3.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@cloudflare/vite-plugin": "^1.23.0",
|
"@types/bun": "^1.3.11",
|
||||||
"@types/node": "^25.2.0",
|
"@vitejs/plugin-vue": "^6.0.5",
|
||||||
"@vitejs/plugin-vue": "^6.0.4",
|
"@vitejs/plugin-vue-jsx": "^5.1.5",
|
||||||
"@vitejs/plugin-vue-jsx": "^5.1.4",
|
"estree-walker": "3.0.3",
|
||||||
"unocss": "^66.6.0",
|
"unocss": "^66.6.7",
|
||||||
"unplugin-auto-import": "^21.0.0",
|
"unplugin-auto-import": "^21.0.0",
|
||||||
"unplugin-vue-components": "^31.0.0",
|
"unplugin-vue-components": "^32.0.0",
|
||||||
"vite": "^7.3.1",
|
"vite": "^8.0.3",
|
||||||
"vite-ssr-components": "^0.5.2",
|
"vite-ssr-components": "^0.5.2"
|
||||||
"wrangler": "^4.62.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1355
public/locales/en/translation.json
Normal file
1355
public/locales/en/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1350
public/locales/vi/translation.json
Normal file
1350
public/locales/vi/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
7
scripts/gen-nacl-keys.ts
Normal file
7
scripts/gen-nacl-keys.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// scripts/gen-nacl-keys.ts
|
||||||
|
import nacl from "tweetnacl";
|
||||||
|
|
||||||
|
const kp = nacl.box.keyPair();
|
||||||
|
|
||||||
|
console.log("PUBLIC_KEY_BASE64=", Buffer.from(kp.publicKey).toString("base64"));
|
||||||
|
console.log("SECRET_KEY_BASE64=", Buffer.from(kp.secretKey).toString("base64"));
|
||||||
@@ -1,688 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
/* tslint:disable */
|
|
||||||
// @ts-nocheck
|
|
||||||
/*
|
|
||||||
* ---------------------------------------------------------------
|
|
||||||
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
|
|
||||||
* ## ##
|
|
||||||
* ## AUTHOR: acacode ##
|
|
||||||
* ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
|
|
||||||
* ---------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
import { customFetch } from "@httpClientAdapter";
|
|
||||||
export interface AuthForgotPasswordRequest {
|
|
||||||
email: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AuthLoginRequest {
|
|
||||||
email: string;
|
|
||||||
password: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AuthRegisterRequest {
|
|
||||||
email: string;
|
|
||||||
/** @minLength 6 */
|
|
||||||
password: string;
|
|
||||||
username: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AuthResetPasswordRequest {
|
|
||||||
/** @minLength 6 */
|
|
||||||
new_password: string;
|
|
||||||
token: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ModelPlan {
|
|
||||||
cycle?: string;
|
|
||||||
description?: string;
|
|
||||||
duration_limit?: number;
|
|
||||||
features?: string;
|
|
||||||
id?: string;
|
|
||||||
is_active?: boolean;
|
|
||||||
name?: string;
|
|
||||||
price?: number;
|
|
||||||
quality_limit?: string;
|
|
||||||
storage_limit?: number;
|
|
||||||
upload_limit?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ModelUser {
|
|
||||||
avatar?: string;
|
|
||||||
created_at?: string;
|
|
||||||
email?: string;
|
|
||||||
google_id?: string;
|
|
||||||
id?: string;
|
|
||||||
password?: string;
|
|
||||||
plan_id?: string;
|
|
||||||
role?: string;
|
|
||||||
storage_used?: number;
|
|
||||||
updated_at?: string;
|
|
||||||
username?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ModelVideo {
|
|
||||||
created_at?: string;
|
|
||||||
description?: string;
|
|
||||||
duration?: number;
|
|
||||||
format?: string;
|
|
||||||
hls_path?: string;
|
|
||||||
hls_token?: string;
|
|
||||||
id?: string;
|
|
||||||
name?: string;
|
|
||||||
processing_status?: string;
|
|
||||||
size?: number;
|
|
||||||
status?: string;
|
|
||||||
storage_type?: string;
|
|
||||||
thumbnail?: string;
|
|
||||||
title?: string;
|
|
||||||
updated_at?: string;
|
|
||||||
url?: string;
|
|
||||||
user_id?: string;
|
|
||||||
views?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PaymentCreatePaymentRequest {
|
|
||||||
amount: number;
|
|
||||||
plan_id: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ResponseResponse {
|
|
||||||
code?: number;
|
|
||||||
message?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface VideoCreateVideoRequest {
|
|
||||||
description?: string;
|
|
||||||
/** Maybe client knows, or we process later */
|
|
||||||
duration?: number;
|
|
||||||
format?: string;
|
|
||||||
size: number;
|
|
||||||
title: string;
|
|
||||||
/** The S3 Key or Full URL */
|
|
||||||
url: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface VideoUploadURLRequest {
|
|
||||||
content_type: string;
|
|
||||||
filename: string;
|
|
||||||
size: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type QueryParamsType = Record<string | number, any>;
|
|
||||||
export type ResponseFormat = keyof Omit<Body, "body" | "bodyUsed">;
|
|
||||||
|
|
||||||
export interface FullRequestParams extends Omit<RequestInit, "body"> {
|
|
||||||
/** set parameter to `true` for call `securityWorker` for this request */
|
|
||||||
secure?: boolean;
|
|
||||||
/** request path */
|
|
||||||
path: string;
|
|
||||||
/** content type of request body */
|
|
||||||
type?: ContentType;
|
|
||||||
/** query params */
|
|
||||||
query?: QueryParamsType;
|
|
||||||
/** format of response (i.e. response.json() -> format: "json") */
|
|
||||||
format?: ResponseFormat;
|
|
||||||
/** request body */
|
|
||||||
body?: unknown;
|
|
||||||
/** base url */
|
|
||||||
baseUrl?: string;
|
|
||||||
/** request cancellation token */
|
|
||||||
cancelToken?: CancelToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type RequestParams = Omit<
|
|
||||||
FullRequestParams,
|
|
||||||
"body" | "method" | "query" | "path"
|
|
||||||
>;
|
|
||||||
|
|
||||||
export interface ApiConfig<SecurityDataType = unknown> {
|
|
||||||
baseUrl?: string;
|
|
||||||
baseApiParams?: Omit<RequestParams, "baseUrl" | "cancelToken" | "signal">;
|
|
||||||
securityWorker?: (
|
|
||||||
securityData: SecurityDataType | null,
|
|
||||||
) => Promise<RequestParams | void> | RequestParams | void;
|
|
||||||
customFetch?: typeof fetch;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HttpResponse<D extends unknown, E extends unknown = unknown>
|
|
||||||
extends Response {
|
|
||||||
data: D;
|
|
||||||
error: E;
|
|
||||||
}
|
|
||||||
|
|
||||||
type CancelToken = Symbol | string | number;
|
|
||||||
|
|
||||||
export enum ContentType {
|
|
||||||
Json = "application/json",
|
|
||||||
JsonApi = "application/vnd.api+json",
|
|
||||||
FormData = "multipart/form-data",
|
|
||||||
UrlEncoded = "application/x-www-form-urlencoded",
|
|
||||||
Text = "text/plain",
|
|
||||||
}
|
|
||||||
|
|
||||||
export class HttpClient<SecurityDataType = unknown> {
|
|
||||||
public baseUrl: string = "";
|
|
||||||
private securityData: SecurityDataType | null = null;
|
|
||||||
private securityWorker?: ApiConfig<SecurityDataType>["securityWorker"];
|
|
||||||
private abortControllers = new Map<CancelToken, AbortController>();
|
|
||||||
private customFetch = (...fetchParams: Parameters<typeof fetch>) =>
|
|
||||||
fetch(...fetchParams);
|
|
||||||
|
|
||||||
private baseApiParams: RequestParams = {
|
|
||||||
credentials: "same-origin",
|
|
||||||
headers: {},
|
|
||||||
redirect: "follow",
|
|
||||||
referrerPolicy: "no-referrer",
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(apiConfig: ApiConfig<SecurityDataType> = {}) {
|
|
||||||
Object.assign(this, apiConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
public setSecurityData = (data: SecurityDataType | null) => {
|
|
||||||
this.securityData = data;
|
|
||||||
};
|
|
||||||
|
|
||||||
protected encodeQueryParam(key: string, value: any) {
|
|
||||||
const encodedKey = encodeURIComponent(key);
|
|
||||||
return `${encodedKey}=${encodeURIComponent(typeof value === "number" ? value : `${value}`)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected addQueryParam(query: QueryParamsType, key: string) {
|
|
||||||
return this.encodeQueryParam(key, query[key]);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected addArrayQueryParam(query: QueryParamsType, key: string) {
|
|
||||||
const value = query[key];
|
|
||||||
return value.map((v: any) => this.encodeQueryParam(key, v)).join("&");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected toQueryString(rawQuery?: QueryParamsType): string {
|
|
||||||
const query = rawQuery || {};
|
|
||||||
const keys = Object.keys(query).filter(
|
|
||||||
(key) => "undefined" !== typeof query[key],
|
|
||||||
);
|
|
||||||
return keys
|
|
||||||
.map((key) =>
|
|
||||||
Array.isArray(query[key])
|
|
||||||
? this.addArrayQueryParam(query, key)
|
|
||||||
: this.addQueryParam(query, key),
|
|
||||||
)
|
|
||||||
.join("&");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected addQueryParams(rawQuery?: QueryParamsType): string {
|
|
||||||
const queryString = this.toQueryString(rawQuery);
|
|
||||||
return queryString ? `?${queryString}` : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private contentFormatters: Record<ContentType, (input: any) => any> = {
|
|
||||||
[ContentType.Json]: (input: any) =>
|
|
||||||
input !== null && (typeof input === "object" || typeof input === "string")
|
|
||||||
? JSON.stringify(input)
|
|
||||||
: input,
|
|
||||||
[ContentType.JsonApi]: (input: any) =>
|
|
||||||
input !== null && (typeof input === "object" || typeof input === "string")
|
|
||||||
? JSON.stringify(input)
|
|
||||||
: input,
|
|
||||||
[ContentType.Text]: (input: any) =>
|
|
||||||
input !== null && typeof input !== "string"
|
|
||||||
? JSON.stringify(input)
|
|
||||||
: input,
|
|
||||||
[ContentType.FormData]: (input: any) => {
|
|
||||||
if (input instanceof FormData) {
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.keys(input || {}).reduce((formData, key) => {
|
|
||||||
const property = input[key];
|
|
||||||
formData.append(
|
|
||||||
key,
|
|
||||||
property instanceof Blob
|
|
||||||
? property
|
|
||||||
: typeof property === "object" && property !== null
|
|
||||||
? JSON.stringify(property)
|
|
||||||
: `${property}`,
|
|
||||||
);
|
|
||||||
return formData;
|
|
||||||
}, new FormData());
|
|
||||||
},
|
|
||||||
[ContentType.UrlEncoded]: (input: any) => this.toQueryString(input),
|
|
||||||
};
|
|
||||||
|
|
||||||
protected mergeRequestParams(
|
|
||||||
params1: RequestParams,
|
|
||||||
params2?: RequestParams,
|
|
||||||
): RequestParams {
|
|
||||||
return {
|
|
||||||
...this.baseApiParams,
|
|
||||||
...params1,
|
|
||||||
...(params2 || {}),
|
|
||||||
headers: {
|
|
||||||
...(this.baseApiParams.headers || {}),
|
|
||||||
...(params1.headers || {}),
|
|
||||||
...((params2 && params2.headers) || {}),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected createAbortSignal = (
|
|
||||||
cancelToken: CancelToken,
|
|
||||||
): AbortSignal | undefined => {
|
|
||||||
if (this.abortControllers.has(cancelToken)) {
|
|
||||||
const abortController = this.abortControllers.get(cancelToken);
|
|
||||||
if (abortController) {
|
|
||||||
return abortController.signal;
|
|
||||||
}
|
|
||||||
return void 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const abortController = new AbortController();
|
|
||||||
this.abortControllers.set(cancelToken, abortController);
|
|
||||||
return abortController.signal;
|
|
||||||
};
|
|
||||||
|
|
||||||
public abortRequest = (cancelToken: CancelToken) => {
|
|
||||||
const abortController = this.abortControllers.get(cancelToken);
|
|
||||||
|
|
||||||
if (abortController) {
|
|
||||||
abortController.abort();
|
|
||||||
this.abortControllers.delete(cancelToken);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public request = async <T = any, E = any>({
|
|
||||||
body,
|
|
||||||
secure,
|
|
||||||
path,
|
|
||||||
type,
|
|
||||||
query,
|
|
||||||
format,
|
|
||||||
baseUrl,
|
|
||||||
cancelToken,
|
|
||||||
...params
|
|
||||||
}: FullRequestParams): Promise<HttpResponse<T, E>> => {
|
|
||||||
const secureParams =
|
|
||||||
((typeof secure === "boolean" ? secure : this.baseApiParams.secure) &&
|
|
||||||
this.securityWorker &&
|
|
||||||
(await this.securityWorker(this.securityData))) ||
|
|
||||||
{};
|
|
||||||
const requestParams = this.mergeRequestParams(params, secureParams);
|
|
||||||
const queryString = query && this.toQueryString(query);
|
|
||||||
const payloadFormatter = this.contentFormatters[type || ContentType.Json];
|
|
||||||
const responseFormat = format || requestParams.format;
|
|
||||||
|
|
||||||
return this.customFetch(
|
|
||||||
`${baseUrl || this.baseUrl || ""}${path}${queryString ? `?${queryString}` : ""}`,
|
|
||||||
{
|
|
||||||
...requestParams,
|
|
||||||
headers: {
|
|
||||||
...(requestParams.headers || {}),
|
|
||||||
...(type && type !== ContentType.FormData
|
|
||||||
? { "Content-Type": type }
|
|
||||||
: {}),
|
|
||||||
},
|
|
||||||
signal:
|
|
||||||
(cancelToken
|
|
||||||
? this.createAbortSignal(cancelToken)
|
|
||||||
: requestParams.signal) || null,
|
|
||||||
body:
|
|
||||||
typeof body === "undefined" || body === null
|
|
||||||
? null
|
|
||||||
: payloadFormatter(body)
|
|
||||||
},
|
|
||||||
).then(async (response) => {
|
|
||||||
const r = response as HttpResponse<T, E>;
|
|
||||||
r.data = null as unknown as T;
|
|
||||||
r.error = null as unknown as E;
|
|
||||||
|
|
||||||
const responseToParse = responseFormat ? response.clone() : response;
|
|
||||||
const data = !responseFormat
|
|
||||||
? r
|
|
||||||
: await responseToParse[responseFormat]()
|
|
||||||
.then((data) => {
|
|
||||||
if (r.ok) {
|
|
||||||
r.data = data;
|
|
||||||
} else {
|
|
||||||
r.error = data;
|
|
||||||
}
|
|
||||||
return r;
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
r.error = e;
|
|
||||||
return r;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (cancelToken) {
|
|
||||||
this.abortControllers.delete(cancelToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.ok) throw data;
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @title Stream API
|
|
||||||
* @version 1.0
|
|
||||||
* @license Apache 2.0 (http://www.apache.org/licenses/LICENSE-2.0.html)
|
|
||||||
* @termsOfService http://swagger.io/terms/
|
|
||||||
* @contact API Support <support@swagger.io> (http://www.swagger.io/support)
|
|
||||||
*
|
|
||||||
* This is the API server for Stream application.
|
|
||||||
*/
|
|
||||||
export class Api<
|
|
||||||
SecurityDataType extends unknown,
|
|
||||||
> extends HttpClient<SecurityDataType> {
|
|
||||||
auth = {
|
|
||||||
/**
|
|
||||||
* @description Request password reset link
|
|
||||||
*
|
|
||||||
* @tags auth
|
|
||||||
* @name ForgotPasswordCreate
|
|
||||||
* @summary Forgot Password
|
|
||||||
* @request POST:/auth/forgot-password
|
|
||||||
*/
|
|
||||||
forgotPasswordCreate: (
|
|
||||||
request: AuthForgotPasswordRequest,
|
|
||||||
params: RequestParams = {},
|
|
||||||
) =>
|
|
||||||
this.request<ResponseResponse, ResponseResponse>({
|
|
||||||
path: `/auth/forgot-password`,
|
|
||||||
method: "POST",
|
|
||||||
body: request,
|
|
||||||
type: ContentType.Json,
|
|
||||||
format: "json",
|
|
||||||
...params,
|
|
||||||
}),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Callback for Google Login
|
|
||||||
*
|
|
||||||
* @tags auth
|
|
||||||
* @name GoogleCallbackList
|
|
||||||
* @summary Google Callback
|
|
||||||
* @request GET:/auth/google/callback
|
|
||||||
*/
|
|
||||||
googleCallbackList: (params: RequestParams = {}) =>
|
|
||||||
this.request<
|
|
||||||
ResponseResponse & {
|
|
||||||
data?: ModelUser;
|
|
||||||
},
|
|
||||||
ResponseResponse
|
|
||||||
>({
|
|
||||||
path: `/auth/google/callback`,
|
|
||||||
method: "GET",
|
|
||||||
...params,
|
|
||||||
}),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Redirect to Google for Login
|
|
||||||
*
|
|
||||||
* @tags auth
|
|
||||||
* @name GoogleLoginList
|
|
||||||
* @summary Google Login
|
|
||||||
* @request GET:/auth/google/login
|
|
||||||
*/
|
|
||||||
googleLoginList: (params: RequestParams = {}) =>
|
|
||||||
this.request<any, void>({
|
|
||||||
path: `/auth/google/login`,
|
|
||||||
method: "GET",
|
|
||||||
...params,
|
|
||||||
}),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Login with email and password
|
|
||||||
*
|
|
||||||
* @tags auth
|
|
||||||
* @name LoginCreate
|
|
||||||
* @summary Login
|
|
||||||
* @request POST:/auth/login
|
|
||||||
*/
|
|
||||||
loginCreate: (request: AuthLoginRequest, params: RequestParams = {}) =>
|
|
||||||
this.request<
|
|
||||||
ResponseResponse & {
|
|
||||||
data?: ModelUser;
|
|
||||||
},
|
|
||||||
ResponseResponse
|
|
||||||
>({
|
|
||||||
path: `/auth/login`,
|
|
||||||
method: "POST",
|
|
||||||
body: request,
|
|
||||||
type: ContentType.Json,
|
|
||||||
format: "json",
|
|
||||||
...params,
|
|
||||||
}),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Logout user and clear cookies
|
|
||||||
*
|
|
||||||
* @tags auth
|
|
||||||
* @name LogoutCreate
|
|
||||||
* @summary Logout
|
|
||||||
* @request POST:/auth/logout
|
|
||||||
*/
|
|
||||||
logoutCreate: (params: RequestParams = {}) =>
|
|
||||||
this.request<ResponseResponse, any>({
|
|
||||||
path: `/auth/logout`,
|
|
||||||
method: "POST",
|
|
||||||
type: ContentType.Json,
|
|
||||||
format: "json",
|
|
||||||
...params,
|
|
||||||
}),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Register a new user
|
|
||||||
*
|
|
||||||
* @tags auth
|
|
||||||
* @name RegisterCreate
|
|
||||||
* @summary Register
|
|
||||||
* @request POST:/auth/register
|
|
||||||
*/
|
|
||||||
registerCreate: (
|
|
||||||
request: AuthRegisterRequest,
|
|
||||||
params: RequestParams = {},
|
|
||||||
) =>
|
|
||||||
this.request<ResponseResponse, ResponseResponse>({
|
|
||||||
path: `/auth/register`,
|
|
||||||
method: "POST",
|
|
||||||
body: request,
|
|
||||||
type: ContentType.Json,
|
|
||||||
format: "json",
|
|
||||||
...params,
|
|
||||||
}),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Reset password using token
|
|
||||||
*
|
|
||||||
* @tags auth
|
|
||||||
* @name ResetPasswordCreate
|
|
||||||
* @summary Reset Password
|
|
||||||
* @request POST:/auth/reset-password
|
|
||||||
*/
|
|
||||||
resetPasswordCreate: (
|
|
||||||
request: AuthResetPasswordRequest,
|
|
||||||
params: RequestParams = {},
|
|
||||||
) =>
|
|
||||||
this.request<ResponseResponse, ResponseResponse>({
|
|
||||||
path: `/auth/reset-password`,
|
|
||||||
method: "POST",
|
|
||||||
body: request,
|
|
||||||
type: ContentType.Json,
|
|
||||||
format: "json",
|
|
||||||
...params,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
payments = {
|
|
||||||
/**
|
|
||||||
* @description Create a new payment
|
|
||||||
*
|
|
||||||
* @tags payment
|
|
||||||
* @name PaymentsCreate
|
|
||||||
* @summary Create Payment
|
|
||||||
* @request POST:/payments
|
|
||||||
* @secure
|
|
||||||
*/
|
|
||||||
paymentsCreate: (
|
|
||||||
request: PaymentCreatePaymentRequest,
|
|
||||||
params: RequestParams = {},
|
|
||||||
) =>
|
|
||||||
this.request<ResponseResponse, ResponseResponse>({
|
|
||||||
path: `/payments`,
|
|
||||||
method: "POST",
|
|
||||||
body: request,
|
|
||||||
secure: true,
|
|
||||||
type: ContentType.Json,
|
|
||||||
format: "json",
|
|
||||||
...params,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
plans = {
|
|
||||||
/**
|
|
||||||
* @description Get all active plans
|
|
||||||
*
|
|
||||||
* @tags plan
|
|
||||||
* @name PlansList
|
|
||||||
* @summary List Plans
|
|
||||||
* @request GET:/plans
|
|
||||||
* @secure
|
|
||||||
*/
|
|
||||||
plansList: (params: RequestParams = {}) =>
|
|
||||||
this.request<
|
|
||||||
ResponseResponse & {
|
|
||||||
data: {
|
|
||||||
plans: ModelPlan[];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ResponseResponse
|
|
||||||
>({
|
|
||||||
path: `/plans`,
|
|
||||||
method: "GET",
|
|
||||||
secure: true,
|
|
||||||
format: "json",
|
|
||||||
...params,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
videos = {
|
|
||||||
/**
|
|
||||||
* @description Get paginated videos
|
|
||||||
*
|
|
||||||
* @tags video
|
|
||||||
* @name VideosList
|
|
||||||
* @summary List Videos
|
|
||||||
* @request GET:/videos
|
|
||||||
* @secure
|
|
||||||
*/
|
|
||||||
videosList: (
|
|
||||||
query?: {
|
|
||||||
/**
|
|
||||||
* Page number
|
|
||||||
* @default 1
|
|
||||||
*/
|
|
||||||
page?: number;
|
|
||||||
/**
|
|
||||||
* Page size
|
|
||||||
* @default 10
|
|
||||||
*/
|
|
||||||
limit?: number;
|
|
||||||
},
|
|
||||||
params: RequestParams = {},
|
|
||||||
) =>
|
|
||||||
this.request<ResponseResponse & {
|
|
||||||
data: {
|
|
||||||
limit: number;
|
|
||||||
page: number;
|
|
||||||
total: number;
|
|
||||||
videos: ModelVideo[];
|
|
||||||
}
|
|
||||||
}, ResponseResponse>({
|
|
||||||
path: `/videos`,
|
|
||||||
method: "GET",
|
|
||||||
query: query,
|
|
||||||
secure: true,
|
|
||||||
format: "json",
|
|
||||||
...params,
|
|
||||||
}),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Create video record after upload
|
|
||||||
*
|
|
||||||
* @tags video
|
|
||||||
* @name VideosCreate
|
|
||||||
* @summary Create Video
|
|
||||||
* @request POST:/videos
|
|
||||||
* @secure
|
|
||||||
*/
|
|
||||||
videosCreate: (
|
|
||||||
request: VideoCreateVideoRequest,
|
|
||||||
params: RequestParams = {},
|
|
||||||
) =>
|
|
||||||
this.request<
|
|
||||||
ResponseResponse & {
|
|
||||||
data?: ModelVideo;
|
|
||||||
},
|
|
||||||
ResponseResponse
|
|
||||||
>({
|
|
||||||
path: `/videos`,
|
|
||||||
method: "POST",
|
|
||||||
body: request,
|
|
||||||
secure: true,
|
|
||||||
type: ContentType.Json,
|
|
||||||
format: "json",
|
|
||||||
...params,
|
|
||||||
}),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Generate presigned URL for video upload
|
|
||||||
*
|
|
||||||
* @tags video
|
|
||||||
* @name UploadUrlCreate
|
|
||||||
* @summary Get Upload URL
|
|
||||||
* @request POST:/videos/upload-url
|
|
||||||
* @secure
|
|
||||||
*/
|
|
||||||
uploadUrlCreate: (
|
|
||||||
request: VideoUploadURLRequest,
|
|
||||||
params: RequestParams = {},
|
|
||||||
) =>
|
|
||||||
this.request<ResponseResponse, ResponseResponse>({
|
|
||||||
path: `/videos/upload-url`,
|
|
||||||
method: "POST",
|
|
||||||
body: request,
|
|
||||||
secure: true,
|
|
||||||
type: ContentType.Json,
|
|
||||||
format: "json",
|
|
||||||
...params,
|
|
||||||
}),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Get video details by ID
|
|
||||||
*
|
|
||||||
* @tags video
|
|
||||||
* @name VideosDetail
|
|
||||||
* @summary Get Video
|
|
||||||
* @request GET:/videos/{id}
|
|
||||||
* @secure
|
|
||||||
*/
|
|
||||||
videosDetail: (id: string, params: RequestParams = {}) =>
|
|
||||||
this.request<
|
|
||||||
ResponseResponse & {
|
|
||||||
data?: ModelVideo;
|
|
||||||
},
|
|
||||||
ResponseResponse
|
|
||||||
>({
|
|
||||||
path: `/videos/${id}`,
|
|
||||||
method: "GET",
|
|
||||||
secure: true,
|
|
||||||
format: "json",
|
|
||||||
...params,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const client = new Api({
|
|
||||||
baseUrl: 'r',
|
|
||||||
// baseUrl: 'https://api.pipic.fun',
|
|
||||||
customFetch
|
|
||||||
});
|
|
||||||
@@ -1,6 +1,78 @@
|
|||||||
export const customFetch = (url: string, options: RequestInit) => {
|
import { TinyRpcClientAdapter, TinyRpcError } from "@hiogawa/tiny-rpc";
|
||||||
return fetch(url, {
|
import { Result } from "@hiogawa/utils";
|
||||||
...options,
|
|
||||||
credentials: "include",
|
const GET_PAYLOAD_PARAM = "payload";
|
||||||
});
|
|
||||||
|
export function httpClientAdapter(opts: {
|
||||||
|
url: string;
|
||||||
|
pathsForGET?: string[];
|
||||||
|
JSON?: Partial<JsonTransformer>;
|
||||||
|
headers?: () => Promise<Record<string, string>> | Record<string, string>;
|
||||||
|
}): TinyRpcClientAdapter {
|
||||||
|
const JSON: JsonTransformer = {
|
||||||
|
parse: globalThis.JSON.parse,
|
||||||
|
stringify: globalThis.JSON.stringify as JsonTransformer["stringify"],
|
||||||
|
...opts.JSON,
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
send: async (data) => {
|
||||||
|
const url = [opts.url, data.path].join("/");
|
||||||
|
const extraHeaders = opts.headers ? await opts.headers() : {};
|
||||||
|
const payload = JSON.stringify(data.args, (headerObj) => {
|
||||||
|
if (headerObj) {
|
||||||
|
Object.assign(extraHeaders, headerObj);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const method = opts.pathsForGET?.includes(data.path)
|
||||||
|
? "GET"
|
||||||
|
: "POST";
|
||||||
|
|
||||||
|
let req: Request;
|
||||||
|
if (method === "GET") {
|
||||||
|
req = new Request(
|
||||||
|
url +
|
||||||
|
"?" +
|
||||||
|
new URLSearchParams({ [GET_PAYLOAD_PARAM]: payload }),
|
||||||
|
{
|
||||||
|
headers: extraHeaders
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
req = new Request(url, {
|
||||||
|
method: "POST",
|
||||||
|
body: payload,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json; charset=utf-8",
|
||||||
|
...extraHeaders
|
||||||
|
},
|
||||||
|
credentials: "include",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let res: Response;
|
||||||
|
|
||||||
|
res = await fetch(req);
|
||||||
|
if (!res.ok) {
|
||||||
|
// throw new Error(`HTTP error: ${res.status}`);
|
||||||
|
throw new Error(
|
||||||
|
JSON.stringify({
|
||||||
|
status: res.status,
|
||||||
|
statusText: res.statusText,
|
||||||
|
data: { message: await res.text() },
|
||||||
|
internal: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
// throw TinyRpcError.deserialize(res.status);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: Result<unknown, unknown> = JSON.parse(
|
||||||
|
await res.text(),
|
||||||
|
() => Object.fromEntries((res.headers as any).entries() ?? [])
|
||||||
|
);
|
||||||
|
if (!result.ok) {
|
||||||
|
throw TinyRpcError.deserialize(result.value);
|
||||||
|
}
|
||||||
|
return result.value;
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
@@ -1,31 +1,88 @@
|
|||||||
|
import { TinyRpcClientAdapter, TinyRpcError } from "@hiogawa/tiny-rpc";
|
||||||
|
import { Result } from "@hiogawa/utils";
|
||||||
import { tryGetContext } from "hono/context-storage";
|
import { tryGetContext } from "hono/context-storage";
|
||||||
|
|
||||||
|
const GET_PAYLOAD_PARAM = "payload";
|
||||||
export const baseAPIURL = "https://api.pipic.fun";
|
export const baseAPIURL = "https://api.pipic.fun";
|
||||||
export const customFetch = (url: string, options: RequestInit) => {
|
|
||||||
options.credentials = "include";
|
|
||||||
const c = tryGetContext<any>();
|
|
||||||
if (!c) {
|
|
||||||
throw new Error("Hono context not found in SSR");
|
|
||||||
}
|
|
||||||
// Merge headers properly - keep original options.headers and add request headers
|
|
||||||
const reqHeaders = new Headers(c.req.header());
|
|
||||||
// Remove headers that shouldn't be forwarded
|
|
||||||
reqHeaders.delete("host");
|
|
||||||
reqHeaders.delete("connection");
|
|
||||||
|
|
||||||
const mergedHeaders: Record<string, string> = {};
|
export function httpClientAdapter(opts: {
|
||||||
reqHeaders.forEach((value, key) => {
|
url: string;
|
||||||
mergedHeaders[key] = value;
|
pathsForGET?: string[];
|
||||||
});
|
JSON?: Partial<JsonTransformer>;
|
||||||
options.headers = {
|
headers?: () => Promise<Record<string, string>> | Record<string, string>;
|
||||||
...mergedHeaders,
|
}): TinyRpcClientAdapter {
|
||||||
...(options.headers as Record<string, string>),
|
const JSON: JsonTransformer = {
|
||||||
|
parse: globalThis.JSON.parse,
|
||||||
|
stringify: globalThis.JSON.stringify as JsonTransformer["stringify"],
|
||||||
|
...opts.JSON,
|
||||||
};
|
};
|
||||||
|
return {
|
||||||
|
send: async (data) => {
|
||||||
|
const url = [opts.url, data.path].join("/");
|
||||||
|
const extraHeaders = opts.headers ? await opts.headers() : {};
|
||||||
|
const payload = JSON.stringify(data.args, (headerObj) => {
|
||||||
|
if (headerObj) {
|
||||||
|
Object.assign(extraHeaders, headerObj);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const method = opts.pathsForGET?.includes(data.path)
|
||||||
|
? "GET"
|
||||||
|
: "POST";
|
||||||
|
let req: Request;
|
||||||
|
if (method === "GET") {
|
||||||
|
req = new Request(
|
||||||
|
url +
|
||||||
|
"?" +
|
||||||
|
new URLSearchParams({ [GET_PAYLOAD_PARAM]: payload }),
|
||||||
|
{
|
||||||
|
headers: extraHeaders
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
req = new Request(url, {
|
||||||
|
method: "POST",
|
||||||
|
body: payload,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json; charset=utf-8",
|
||||||
|
...extraHeaders,
|
||||||
|
},
|
||||||
|
credentials: "include",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let res: Response;
|
||||||
|
if (import.meta.env.SSR) {
|
||||||
|
const c = tryGetContext<any>();
|
||||||
|
if (!c) {
|
||||||
|
throw new Error("Hono context not found in SSR");
|
||||||
|
}
|
||||||
|
Object.entries(c.req.header()).forEach(([k, v]) => {
|
||||||
|
req.headers.append(k, v);
|
||||||
|
});
|
||||||
|
res = await c.get("fetch")(req);
|
||||||
|
} else {
|
||||||
|
res = await fetch(req);
|
||||||
|
}
|
||||||
|
|
||||||
const apiUrl = [baseAPIURL, url.replace(/^r/, "")].join("");
|
if (!res.ok) {
|
||||||
return fetch(apiUrl, options).then(async (res) => {
|
// throw new Error(`HTTP error: ${res.status}`);
|
||||||
res.headers.getSetCookie()?.forEach((cookie) => {
|
throw new Error(
|
||||||
c.header("Set-Cookie", cookie);
|
JSON.stringify({
|
||||||
});
|
status: res.status,
|
||||||
return res;
|
statusText: res.statusText,
|
||||||
});
|
data: { message: await res.text() },
|
||||||
};
|
internal: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
// throw TinyRpcError.deserialize(res.status);
|
||||||
|
}
|
||||||
|
const result: Result<unknown, unknown> = JSON.parse(
|
||||||
|
await res.text(),
|
||||||
|
() => Object.fromEntries((res.headers as any).entries() ?? [])
|
||||||
|
);
|
||||||
|
if (!result.ok) {
|
||||||
|
throw TinyRpcError.deserialize(result.value);
|
||||||
|
}
|
||||||
|
return result.value;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
39
src/api/rpcclient.ts
Normal file
39
src/api/rpcclient.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import type { RpcRoutes } from "@/server/routes/rpc";
|
||||||
|
import { proxyTinyRpc } from "@hiogawa/tiny-rpc";
|
||||||
|
import { httpClientAdapter } from "@httpClientAdapter";
|
||||||
|
|
||||||
|
const endpoint = "/rpc";
|
||||||
|
const publicEndpoint = "/rpc-public";
|
||||||
|
const url = import.meta.env.SSR ? "http://localhost" : "";
|
||||||
|
const publicMethods = ["login", "register", "forgotPassword", "resetPassword", "getGoogleLoginUrl"];
|
||||||
|
// src/client/trpc-client-transformer.ts
|
||||||
|
import {
|
||||||
|
clientJSON
|
||||||
|
} from "@/shared/secure-json-transformer";
|
||||||
|
|
||||||
|
|
||||||
|
// export function createTrpcClientTransformer(cfg: ServerPublicKeyConfig) {
|
||||||
|
// return {
|
||||||
|
// input: ,
|
||||||
|
// output: superjson,
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
// const secureConfig = await fetch("/trpc-secure-config").then((r) => r.json());
|
||||||
|
export const client = proxyTinyRpc<RpcRoutes>({
|
||||||
|
adapter: {
|
||||||
|
send: async (data) => {
|
||||||
|
const targetEndpoint = publicMethods.includes(data.path) ? publicEndpoint : endpoint;
|
||||||
|
return await httpClientAdapter({
|
||||||
|
url: `${url}${targetEndpoint}`,
|
||||||
|
pathsForGET: ["health"],
|
||||||
|
JSON: {
|
||||||
|
// parse: clientJSON.parse,
|
||||||
|
parse: (v, fn) => JSON.parse(v),
|
||||||
|
// stringify: clientJSON.stringify,
|
||||||
|
stringify: (v, fn) => JSON.stringify(v),
|
||||||
|
},
|
||||||
|
headers: () => Promise.resolve({})
|
||||||
|
}).send(data);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -3,14 +3,20 @@ import 'uno.css';
|
|||||||
import PiniaSharedState from './lib/PiniaSharedState';
|
import PiniaSharedState from './lib/PiniaSharedState';
|
||||||
import { createApp } from './main';
|
import { createApp } from './main';
|
||||||
|
|
||||||
|
const readAppData = () => {
|
||||||
|
return JSON.parse(document.getElementById('__APP_DATA__')?.innerText || '{}') as Record<string, any>;
|
||||||
|
};
|
||||||
|
|
||||||
async function render() {
|
async function render() {
|
||||||
const { app, router, queryCache, pinia } = createApp();
|
const appData = readAppData();
|
||||||
pinia.use(PiniaSharedState({enable: true, initialize: true}));
|
const { app, router, queryCache, pinia } = await createApp(appData.$locale);
|
||||||
hydrateQueryCache(queryCache, (window as any).$colada || {});
|
pinia.use(PiniaSharedState({ enable: true, initialize: true }));
|
||||||
router.isReady().then(() => {
|
hydrateQueryCache(queryCache, appData.$colada || {});
|
||||||
app.mount('body', true)
|
|
||||||
})
|
await router.isReady();
|
||||||
|
app.mount('body', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
render().catch((error) => {
|
render().catch((error) => {
|
||||||
console.error('Error during app initialization:', error)
|
console.error('Error during app initialization:', error);
|
||||||
})
|
});
|
||||||
|
|||||||
23
src/components/AppTopLoadingBar.vue
Normal file
23
src/components/AppTopLoadingBar.vue
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useRouteLoading } from '@/composables/useRouteLoading';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
const { visible, progress } = useRouteLoading()
|
||||||
|
|
||||||
|
const barStyle = computed(() => ({
|
||||||
|
transform: `scaleX(${progress.value / 100})`,
|
||||||
|
opacity: visible.value ? '1' : '0',
|
||||||
|
}))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="pointer-events-none fixed inset-x-0 top-0 z-[9999] h-0.75"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="h-full origin-left rounded-r-full bg-primary/50 transition-[transform,opacity] duration-200 ease-out"
|
||||||
|
:style="barStyle"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
// return () => null;
|
// return () => null;
|
||||||
// });
|
// });
|
||||||
|
|
||||||
import { ref, onMounted } from "vue";
|
import { defineComponent, onMounted, ref } from "vue";
|
||||||
const ClientOnly = defineComponent({
|
const ClientOnly = defineComponent({
|
||||||
name: "ClientOnly",
|
name: "ClientOnly",
|
||||||
setup(_p, { slots }) {
|
setup(_p, { slots }) {
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import Upload from "@/routes/upload/Upload.vue";
|
||||||
import DashboardNav from "./DashboardNav.vue";
|
import DashboardNav from "./DashboardNav.vue";
|
||||||
import GlobalUploadIndicator from "./GlobalUploadIndicator.vue";
|
import GlobalUploadIndicator from "./GlobalUploadIndicator.vue";
|
||||||
import Upload from "@/routes/upload/Upload.vue";
|
import PopupAdsRuntime from "./PopupAdsRuntime.vue";
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DashboardNav />
|
<DashboardNav />
|
||||||
<main class="flex flex-1 flex-col transition-all duration-300 ease-in-out bg-page md:ps-18">
|
<main class="flex flex-1 flex-col transition-all duration-300 ease-in-out bg-white md:ps-18">
|
||||||
<div class=":uno: flex-1 overflow-auto p-4 bg-page rounded-lg md:(mr-2 mb-2) min-h-[calc(100vh-8rem)]">
|
<div class=":uno: flex-1 overflow-auto p-4 bg-white rounded-lg md:(mr-2 mb-2) min-h-[calc(100vh-8rem)]">
|
||||||
<router-view v-slot="{ Component }">
|
<router-view v-slot="{ Component }">
|
||||||
<Transition enter-active-class="transition-all duration-300 ease-in-out"
|
<Transition enter-active-class="transition-all duration-300 ease-in-out"
|
||||||
enter-from-class="opacity-0 transform translate-y-4"
|
enter-from-class="opacity-0 transform translate-y-4"
|
||||||
@@ -22,5 +23,6 @@ import Upload from "@/routes/upload/Upload.vue";
|
|||||||
</div>
|
</div>
|
||||||
<GlobalUploadIndicator />
|
<GlobalUploadIndicator />
|
||||||
<Upload />
|
<Upload />
|
||||||
|
<PopupAdsRuntime />
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,51 +1,86 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import Bell from "@/components/icons/Bell.vue";
|
import Bell from "@/components/icons/Bell.vue";
|
||||||
import Home from "@/components/icons/Home.vue";
|
import Home from "@/components/icons/Home.vue";
|
||||||
import Video from "@/components/icons/Video.vue";
|
|
||||||
import SettingsIcon from "@/components/icons/SettingsIcon.vue";
|
import SettingsIcon from "@/components/icons/SettingsIcon.vue";
|
||||||
// import Upload from "@/components/icons/Upload.vue";
|
import Video from "@/components/icons/Video.vue";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { createStaticVNode, ref } from "vue";
|
import { useNotifications } from "@/composables/useNotifications";
|
||||||
|
import { useAuthStore } from "@/stores/auth";
|
||||||
|
import { useTranslation } from "i18next-vue";
|
||||||
|
import { computed, createStaticVNode, h, ref } from "vue";
|
||||||
import NotificationDrawer from "./NotificationDrawer.vue";
|
import NotificationDrawer from "./NotificationDrawer.vue";
|
||||||
|
import Chart from "./icons/Chart.vue";
|
||||||
|
|
||||||
const className = ":uno: w-12 h-12 p-2 rounded-2xl hover:bg-primary/15 flex press-animated items-center justify-center shrink-0";
|
const className = ":uno: w-12 h-12 p-2 rounded-2xl hover:bg-primary/15 flex press-animated items-center justify-center shrink-0";
|
||||||
const homeHoist = createStaticVNode(`<img class="h-8 w-8" src="/apple-touch-icon.png" alt="Logo" />`, 1);
|
const homeHoist = createStaticVNode(`<img class="h-8 w-8" src="/apple-touch-icon.png" alt="Logo" />`, 1);
|
||||||
const notificationPopover = ref<InstanceType<typeof NotificationDrawer>>();
|
const notificationPopover = ref<InstanceType<typeof NotificationDrawer>>();
|
||||||
const isNotificationOpen = ref(false);
|
const isNotificationOpen = ref(false);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const notificationStore = useNotifications();
|
||||||
|
const unreadCount = computed(() => notificationStore.unreadCount.value);
|
||||||
|
|
||||||
const handleNotificationClick = (event: Event) => {
|
const handleNotificationClick = (event: Event) => {
|
||||||
notificationPopover.value?.toggle(event);
|
notificationPopover.value?.toggle(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
const links = [
|
const links = computed<Record<string, any>>(() => {
|
||||||
{ href: "/#home", label: "app", icon: homeHoist, type: "btn", className },
|
const baseLinks = [
|
||||||
{ href: "/", label: "Overview", icon: Home, type: "a", className },
|
{
|
||||||
// { href: "/upload", label: "Upload", icon: Upload, type: "a", className },
|
id: "home",
|
||||||
{ href: "/videos", label: "Videos", icon: Video, type: "a", className },
|
href: "/#home", label: "app", icon: homeHoist, action: () => { }, className
|
||||||
{ href: "/notification", label: "Notification", icon: Bell, type: "btn", className, action: handleNotificationClick, isActive: isNotificationOpen },
|
},
|
||||||
{ href: "/settings", label: "Settings", icon: SettingsIcon, type: "a", className },
|
{
|
||||||
];
|
id: "overview",
|
||||||
|
href: "/", label: t("nav.overview"), icon: Home, action: null, className
|
||||||
|
},
|
||||||
//v-tooltip="i.label"
|
{
|
||||||
|
id: "videos",
|
||||||
|
href: "/videos", label: t("nav.videos"), icon: Video, action: null, className
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "analytics",
|
||||||
|
href: "/analytics", label: t("nav.analytics"), icon: Chart, action: null, className
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "notification",
|
||||||
|
href: "/notification",
|
||||||
|
label: t("nav.notification"),
|
||||||
|
icon: Bell,
|
||||||
|
className: cn(
|
||||||
|
className,
|
||||||
|
isNotificationOpen.value && "bg-primary/15",
|
||||||
|
),
|
||||||
|
action: handleNotificationClick,
|
||||||
|
isActive: isNotificationOpen,
|
||||||
|
expandComponent: unreadCount.value > 0 ? () => h('span', {
|
||||||
|
class: 'absolute -top-2 -right-2 min-w-4 h-4 text-xs font-bold text-white bg-red rounded-full flex items-center justify-center'
|
||||||
|
}, [unreadCount.value > 9 ? '9+' : unreadCount.value]) : undefined
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "settings",
|
||||||
|
href: "/settings", label: t("nav.settings"), icon: SettingsIcon, action: null, className
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
return baseLinks;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<header
|
<header
|
||||||
class=":uno: fixed left-0 flex flex-col items-center pt-4 gap-6 z-41 max-h-screen h-screen bg-muted transition-all duration-300 ease-in-out w-18 items-center">
|
class=":uno: fixed left-0 flex flex-col items-center pt-4 gap-6 z-41 max-h-screen h-screen bg-header transition-all duration-300 ease-in-out w-18 items-center border-r border-border text-foreground/60">
|
||||||
|
<template v-for="i in links" :key="i.href">
|
||||||
<template v-for="i in links" :key="i.label">
|
<component :name="i.label" :is="i.action ? 'div' : 'router-link'" v-bind="i.action ? {} : { to: i.href }"
|
||||||
<component :name="i.label" :is="i.type === 'a' ? 'router-link' : 'div'"
|
@click="i.action && i.action($event)" :class="cn(
|
||||||
v-bind="i.type === 'a' ? { to: i.href } : {}"
|
i.className,
|
||||||
@click="i.action && i.action($event)"
|
($route.path === i.href || $route.path.startsWith(i.href + '/') || i.isActive?.value) && 'bg-primary/15 text-primary',
|
||||||
:class="cn(
|
)">
|
||||||
i.className,
|
<div class="relative">
|
||||||
($route.path === i.href || $route.path.startsWith(i.href+'/') || i.isActive?.value) && 'bg-primary/15'
|
<component :is="i.icon" class="w-6 h-6 shrink-0"
|
||||||
)">
|
:filled="$route.path === i.href || $route.path.startsWith(i.href + '/') || i.isActive?.value" />
|
||||||
<component :is="i.icon" class="w-6 h-6 shrink-0"
|
<component v-if="i.expandComponent" :is="i.expandComponent" />
|
||||||
:filled="$route.path === i.href || $route.path.startsWith(i.href+'/') || i.isActive?.value" />
|
</div>
|
||||||
</component>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
</header>
|
</header>
|
||||||
<NotificationDrawer ref="notificationPopover" @change="(val) => isNotificationOpen = val" />
|
<NotificationDrawer ref="notificationPopover" @change="(val) => (isNotificationOpen = val)" />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -2,12 +2,14 @@
|
|||||||
import { useUploadQueue } from '@/composables/useUploadQueue';
|
import { useUploadQueue } from '@/composables/useUploadQueue';
|
||||||
import UploadQueueItem from '@/routes/upload/components/UploadQueueItem.vue';
|
import UploadQueueItem from '@/routes/upload/components/UploadQueueItem.vue';
|
||||||
import { useUIState } from '@/stores/uiState';
|
import { useUIState } from '@/stores/uiState';
|
||||||
import { computed, ref } from 'vue';
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
import { computed, ref, watch } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { items, completeCount, pendingCount, startQueue, removeItem, cancelItem, removeAll } = useUploadQueue();
|
const { items, completeCount, pendingCount, startQueue, removeItem, cancelItem, removeAll } = useUploadQueue();
|
||||||
const uiState = useUIState();
|
const uiState = useUIState();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const isCollapsed = ref(false);
|
const isCollapsed = ref(false);
|
||||||
|
|
||||||
@@ -28,13 +30,13 @@ const isAllDone = computed(() =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
const statusText = computed(() => {
|
const statusText = computed(() => {
|
||||||
if (isAllDone.value) return 'All done';
|
if (isAllDone.value) return t('upload.indicator.allDone');
|
||||||
if (isUploading.value) {
|
if (isUploading.value) {
|
||||||
const count = items.value.filter(i => i.status === 'uploading' || i.status === 'fetching').length;
|
const count = items.value.filter(i => i.status === 'uploading' || i.status === 'fetching').length;
|
||||||
return `Uploading ${count} file${count !== 1 ? 's' : ''}...`;
|
return t('upload.indicator.uploading', { count });
|
||||||
}
|
}
|
||||||
if (pendingCount.value > 0) return `${pendingCount.value} file${pendingCount.value !== 1 ? 's' : ''} waiting`;
|
if (pendingCount.value > 0) return t('upload.indicator.waiting', { count: pendingCount.value });
|
||||||
return 'Processing...';
|
return t('upload.queueItem.status.processing');
|
||||||
});
|
});
|
||||||
const isDoneWithErrors = computed(() =>
|
const isDoneWithErrors = computed(() =>
|
||||||
isAllDone.value &&
|
isAllDone.value &&
|
||||||
@@ -87,7 +89,7 @@ watch(isAllDone, (newItems) => {
|
|||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
<p class="text-sm font-semibold leading-tight truncate">{{ statusText }}</p>
|
<p class="text-sm font-semibold leading-tight truncate">{{ statusText }}</p>
|
||||||
<p class="text-xs text-slate-400 leading-tight mt-0.5">
|
<p class="text-xs text-slate-400 leading-tight mt-0.5">
|
||||||
{{ completeCount }} of {{ items.length }} complete
|
{{ t('upload.indicator.completeProgress', { complete: completeCount, total: items.length }) }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -100,17 +102,17 @@ watch(isAllDone, (newItems) => {
|
|||||||
stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<polygon points="5 3 19 12 5 21 5 3" />
|
<polygon points="5 3 19 12 5 21 5 3" />
|
||||||
</svg>
|
</svg>
|
||||||
Start
|
{{ t('upload.indicator.start') }}
|
||||||
</button>
|
</button>
|
||||||
<button v-else-if="isDoneWithErrors" @click.stop="doneUpload"
|
<button v-else-if="isDoneWithErrors" @click.stop="doneUpload"
|
||||||
class="flex items-center gap-1.5 text-xs font-semibold px-3 py-1.5 bg-green-500 hover:bg-green-500/80 text-white rounded-lg transition-all">
|
class="flex items-center gap-1.5 text-xs font-semibold px-3 py-1.5 bg-green-500 hover:bg-green-500/80 text-white rounded-lg transition-all">
|
||||||
View Videos
|
{{ t('upload.indicator.viewVideos') }}
|
||||||
</button>
|
</button>
|
||||||
<!-- Clear queue -->
|
<!-- Clear queue -->
|
||||||
<!-- Add more files -->
|
<!-- Add more files -->
|
||||||
<button @click.stop="uiState.uploadDialogVisible = true"
|
<button @click.stop="uiState.uploadDialogVisible = true"
|
||||||
class="w-7 h-7 flex items-center justify-center text-slate-400 hover:text-white hover:bg-white/10 rounded-lg transition-all"
|
class="w-7 h-7 flex items-center justify-center text-slate-400 hover:text-white hover:bg-white/10 rounded-lg transition-all"
|
||||||
title="Add more files">
|
:title="t('upload.indicator.addMoreFiles')">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" viewBox="0 0 24 24" fill="none"
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" viewBox="0 0 24 24" fill="none"
|
||||||
stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path d="M5 12h14" />
|
<path d="M5 12h14" />
|
||||||
|
|||||||
@@ -1,122 +1,52 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import NotificationItem from '@/routes/notification/components/NotificationItem.vue';
|
import NotificationItem from '@/routes/notification/components/NotificationItem.vue';
|
||||||
|
import { useNotifications } from '@/composables/useNotifications';
|
||||||
import { onClickOutside } from '@vueuse/core';
|
import { onClickOutside } from '@vueuse/core';
|
||||||
import { computed, onMounted, ref, watch } from 'vue';
|
import { computed, onMounted, ref, watch } from 'vue';
|
||||||
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
import BellOff from './icons/BellOff.vue';
|
||||||
|
|
||||||
// Ensure client-side only rendering to avoid hydration mismatch
|
|
||||||
const isMounted = ref(false);
|
const isMounted = ref(false);
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
isMounted.value = true;
|
isMounted.value = true;
|
||||||
|
void notificationStore.fetchNotifications();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Emit event when visibility changes
|
|
||||||
const emit = defineEmits(['change']);
|
const emit = defineEmits(['change']);
|
||||||
|
|
||||||
type NotificationType = 'info' | 'success' | 'warning' | 'error' | 'video' | 'payment' | 'system';
|
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
id: string;
|
|
||||||
type: NotificationType;
|
|
||||||
title: string;
|
|
||||||
message: string;
|
|
||||||
time: string;
|
|
||||||
read: boolean;
|
|
||||||
actionUrl?: string;
|
|
||||||
actionLabel?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const visible = ref(false);
|
const visible = ref(false);
|
||||||
const drawerRef = ref(null);
|
const drawerRef = ref(null);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const notificationStore = useNotifications();
|
||||||
|
|
||||||
// Mock notifications data
|
const unreadCount = computed(() => notificationStore.unreadCount.value);
|
||||||
const notifications = ref<Notification[]>([
|
const mutableNotifications = computed(() => notificationStore.notifications.value.slice(0, 8));
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
type: 'video',
|
|
||||||
title: 'Video processing complete',
|
|
||||||
message: 'Your video "Summer Vacation 2024" has been successfully processed.',
|
|
||||||
time: '2 min ago',
|
|
||||||
read: false,
|
|
||||||
actionUrl: '/video',
|
|
||||||
actionLabel: 'View'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
type: 'payment',
|
|
||||||
title: 'Payment successful',
|
|
||||||
message: 'Your subscription to Pro Plan has been renewed successfully.',
|
|
||||||
time: '1 hour ago',
|
|
||||||
read: false,
|
|
||||||
actionUrl: '/payments-and-plans',
|
|
||||||
actionLabel: 'Receipt'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '3',
|
|
||||||
type: 'warning',
|
|
||||||
title: 'Storage almost full',
|
|
||||||
message: 'You have used 85% of your storage quota.',
|
|
||||||
time: '3 hours ago',
|
|
||||||
read: false,
|
|
||||||
actionUrl: '/payments-and-plans',
|
|
||||||
actionLabel: 'Upgrade'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '4',
|
|
||||||
type: 'success',
|
|
||||||
title: 'Upload successful',
|
|
||||||
message: 'Your video "Product Demo v2" has been uploaded.',
|
|
||||||
time: '1 day ago',
|
|
||||||
read: true
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
const unreadCount = computed(() => notifications.value.filter(n => !n.read).length);
|
|
||||||
|
|
||||||
const toggle = (event?: Event) => {
|
const toggle = (event?: Event) => {
|
||||||
console.log(event);
|
console.log(event);
|
||||||
// Prevent event propagation to avoid immediate closure by onClickOutside
|
|
||||||
if (event) {
|
|
||||||
// We don't stop propagation here to let other listeners work,
|
|
||||||
// but we might need to ignore the trigger element in onClickOutside
|
|
||||||
// However, since the trigger is outside this component, simple toggle logic works
|
|
||||||
// if we use a small delay or ignore ref.
|
|
||||||
// Best approach: "toggle" usually comes from a button click.
|
|
||||||
}
|
|
||||||
visible.value = !visible.value;
|
visible.value = !visible.value;
|
||||||
console.log(visible.value);
|
if (visible.value && !notificationStore.loaded.value) {
|
||||||
|
void notificationStore.fetchNotifications();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle click outside
|
onClickOutside(drawerRef, () => {
|
||||||
onClickOutside(drawerRef, (event) => {
|
|
||||||
// We can just set visible to false.
|
|
||||||
// Note: If the toggle button is clicked, it might toggle it back on immediately
|
|
||||||
// if the click event propagates.
|
|
||||||
// The user calls `toggle` from the parent's button click handler.
|
|
||||||
// If that button is outside `drawerRef` (which it is), this will fire.
|
|
||||||
// To avoid conflict, we usually check if the target is the trigger.
|
|
||||||
// But we don't have access to the trigger ref here.
|
|
||||||
// A common workaround is to use `ignore` option if we had the ref,
|
|
||||||
// or relying on the fact that if this fires, it sets specific state to false.
|
|
||||||
// If the button click then fires `toggle`, it might set it true again.
|
|
||||||
// Optimization: check if visible is true before closing.
|
|
||||||
if (visible.value) {
|
if (visible.value) {
|
||||||
visible.value = false;
|
visible.value = false;
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
ignore: ['[name="Notification"]'] // Assuming the trigger button has this class or we can suggest adding a class to the trigger
|
ignore: ['[name="Notification"]']
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleMarkRead = (id: string) => {
|
const handleMarkRead = async (id: string) => {
|
||||||
const notification = notifications.value.find(n => n.id === id);
|
await notificationStore.markRead(id);
|
||||||
if (notification) notification.read = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (id: string) => {
|
const handleDelete = async (id: string) => {
|
||||||
notifications.value = notifications.value.filter(n => n.id !== id);
|
await notificationStore.deleteNotification(id);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMarkAllRead = () => {
|
const handleMarkAllRead = async () => {
|
||||||
notifications.value.forEach(n => n.read = true);
|
await notificationStore.markAllRead();
|
||||||
};
|
};
|
||||||
|
|
||||||
watch(visible, (val) => {
|
watch(visible, (val) => {
|
||||||
@@ -134,10 +64,9 @@ defineExpose({ toggle });
|
|||||||
leave-to-class="opacity-0 -translate-x-4">
|
leave-to-class="opacity-0 -translate-x-4">
|
||||||
<div v-if="visible" ref="drawerRef"
|
<div v-if="visible" ref="drawerRef"
|
||||||
class="fixed top-0 left-[80px] bottom-0 w-[380px] bg-white rounded-2xl border border-gray-300 p-3 z-50 flex flex-col shadow-lg my-3">
|
class="fixed top-0 left-[80px] bottom-0 w-[380px] bg-white rounded-2xl border border-gray-300 p-3 z-50 flex flex-col shadow-lg my-3">
|
||||||
<!-- Header -->
|
|
||||||
<div class="flex items-center justify-between p-4">
|
<div class="flex items-center justify-between p-4">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<h3 class="font-semibold text-gray-900">Notifications</h3>
|
<h3 class="font-semibold text-gray-900">{{ t('notification.title') }}</h3>
|
||||||
<span v-if="unreadCount > 0"
|
<span v-if="unreadCount > 0"
|
||||||
class="px-2 py-0.5 text-xs font-medium bg-primary text-white rounded-full">
|
class="px-2 py-0.5 text-xs font-medium bg-primary text-white rounded-full">
|
||||||
{{ unreadCount }}
|
{{ unreadCount }}
|
||||||
@@ -145,49 +74,44 @@ defineExpose({ toggle });
|
|||||||
</div>
|
</div>
|
||||||
<button v-if="unreadCount > 0" @click="handleMarkAllRead"
|
<button v-if="unreadCount > 0" @click="handleMarkAllRead"
|
||||||
class="text-sm text-primary hover:underline font-medium">
|
class="text-sm text-primary hover:underline font-medium">
|
||||||
Mark all read
|
{{ t('notification.actions.markAllRead') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Notification List -->
|
|
||||||
<div class="flex flex-col flex-1 overflow-y-auto gap-2">
|
<div class="flex flex-col flex-1 overflow-y-auto gap-2">
|
||||||
<template v-if="notifications.length > 0">
|
<template v-if="notificationStore.loading.value">
|
||||||
<div v-for="notification in notifications" :key="notification.id"
|
<div v-for="i in 4" :key="i" class="p-4 rounded-xl border border-gray-200 animate-pulse">
|
||||||
|
<div class="flex items-start gap-4">
|
||||||
|
<div class="w-10 h-10 rounded-full bg-gray-200"></div>
|
||||||
|
<div class="flex-1 space-y-2">
|
||||||
|
<div class="h-4 bg-gray-200 rounded w-1/3"></div>
|
||||||
|
<div class="h-3 bg-gray-200 rounded w-2/3"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="mutableNotifications.length > 0">
|
||||||
|
<div v-for="notification in mutableNotifications" :key="notification.id"
|
||||||
class="border-b border-gray-50 last:border-0">
|
class="border-b border-gray-50 last:border-0">
|
||||||
<NotificationItem :notification="notification" @mark-read="handleMarkRead"
|
<NotificationItem :notification="notification" @mark-read="handleMarkRead"
|
||||||
@delete="handleDelete" isDrawer />
|
@delete="handleDelete" isDrawer />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- Empty state -->
|
|
||||||
<div v-else class="py-12 text-center">
|
<div v-else class="py-12 text-center">
|
||||||
<span class="i-lucide-bell-off w-12 h-12 text-gray-300 mx-auto block mb-3"></span>
|
<BellOff class="w-12 h-12 text-gray-300 mx-auto block mb-3" />
|
||||||
<p class="text-gray-500 text-sm">No notifications</p>
|
<p class="text-gray-500 text-sm">{{ t('notification.empty.title') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Footer -->
|
<div v-if="mutableNotifications.length > 0" class="p-3 border-t border-gray-100 bg-gray-50/50">
|
||||||
<div v-if="notifications.length > 0" class="p-3 border-t border-gray-100 bg-gray-50/50">
|
|
||||||
<router-link to="/notification"
|
<router-link to="/notification"
|
||||||
class="block w-full text-center text-sm text-primary font-medium hover:underline"
|
class="block w-full text-center text-sm text-primary font-medium hover:underline"
|
||||||
@click="visible = false">
|
@click="visible = false">
|
||||||
View all notifications
|
{{ t('notification.actions.viewAll') }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- <style>
|
|
||||||
.notification-popover {
|
|
||||||
border-radius: 16px !important;
|
|
||||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.12) !important;
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.08) !important;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification-popover .p-popover-content {
|
|
||||||
padding: 0 !important;
|
|
||||||
}
|
|
||||||
</style> -->
|
|
||||||
|
|||||||
67
src/components/OfflineOverlay.vue
Normal file
67
src/components/OfflineOverlay.vue
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import AppButton from '@/components/ui/AppButton.vue'
|
||||||
|
import { useNetworkStatus } from '@/composables/useNetworkStatus'
|
||||||
|
import { useTranslation } from 'i18next-vue'
|
||||||
|
import { onBeforeUnmount, onMounted } from 'vue'
|
||||||
|
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { isOffline, startListening, stopListening } = useNetworkStatus()
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
startListening()
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
stopListening()
|
||||||
|
})
|
||||||
|
|
||||||
|
function reloadPage() {
|
||||||
|
if (typeof window === 'undefined') return
|
||||||
|
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="isOffline"
|
||||||
|
class="fixed inset-0 z-[10000] flex items-center justify-center bg-slate-950/80 px-6 backdrop-blur-sm"
|
||||||
|
role="alert"
|
||||||
|
aria-live="assertive"
|
||||||
|
>
|
||||||
|
<div class="w-full max-w-md rounded-2xl border border-border bg-white p-8 text-center shadow-2xl">
|
||||||
|
<div class="mx-auto mb-6 flex h-16 w-16 items-center justify-center rounded-full bg-danger/10 text-danger">
|
||||||
|
<svg
|
||||||
|
class="h-8 w-8"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.8"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path d="M2 8.82a15 15 0 0 1 20 0" />
|
||||||
|
<path d="M5 12.86a10 10 0 0 1 14 0" />
|
||||||
|
<path d="M8.5 16.43a5 5 0 0 1 7 0" />
|
||||||
|
<path d="M12 20h.01" />
|
||||||
|
<path d="M3 3l18 18" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 class="text-xl font-semibold text-foreground">
|
||||||
|
{{ t('network.offline.title') }}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p class="mt-3 text-sm leading-6 text-foreground/70">
|
||||||
|
{{ t('network.offline.description') }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="mt-6 flex justify-center">
|
||||||
|
<AppButton @click="reloadPage">
|
||||||
|
{{ t('network.offline.action') }}
|
||||||
|
</AppButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
75
src/components/PopupAdsRuntime.vue
Normal file
75
src/components/PopupAdsRuntime.vue
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { client as rpcClient } from '@/api/rpcclient';
|
||||||
|
import ClientOnly from '@/components/ClientOnly';
|
||||||
|
import { onMounted, onBeforeUnmount } from 'vue';
|
||||||
|
|
||||||
|
let activeItem: any | null = null;
|
||||||
|
let clickHandler: ((event: MouseEvent) => void) | null = null;
|
||||||
|
let scriptNode: HTMLScriptElement | null = null;
|
||||||
|
let triggerCount = 0;
|
||||||
|
|
||||||
|
const triggerKey = (id: string) => `popup_ad_triggers:${id}`;
|
||||||
|
|
||||||
|
const cleanupScript = () => {
|
||||||
|
if (scriptNode?.parentNode) {
|
||||||
|
scriptNode.parentNode.removeChild(scriptNode);
|
||||||
|
}
|
||||||
|
scriptNode = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const attachUrlHandler = () => {
|
||||||
|
if (!activeItem?.id || typeof window === 'undefined') return;
|
||||||
|
const maxTriggers = Number(activeItem.maxTriggersPerSession || 1);
|
||||||
|
triggerCount = Number(sessionStorage.getItem(triggerKey(activeItem.id)) || '0');
|
||||||
|
|
||||||
|
clickHandler = () => {
|
||||||
|
if (!activeItem?.value || triggerCount >= maxTriggers) return;
|
||||||
|
triggerCount += 1;
|
||||||
|
sessionStorage.setItem(triggerKey(activeItem.id), String(triggerCount));
|
||||||
|
window.open(activeItem.value, '_blank', 'noopener,noreferrer');
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('click', clickHandler, { capture: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
const attachScript = () => {
|
||||||
|
if (!activeItem?.value || typeof document === 'undefined') return;
|
||||||
|
cleanupScript();
|
||||||
|
scriptNode = document.createElement('script');
|
||||||
|
scriptNode.async = true;
|
||||||
|
scriptNode.text = activeItem.value;
|
||||||
|
document.body.appendChild(scriptNode);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
const response = await rpcClient.getActivePopupAd();
|
||||||
|
activeItem = response.item || null;
|
||||||
|
if (!activeItem?.isActive) return;
|
||||||
|
|
||||||
|
if (activeItem.type === 'script') {
|
||||||
|
attachScript();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeItem.type === 'url') {
|
||||||
|
attachUrlHandler();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (clickHandler && typeof window !== 'undefined') {
|
||||||
|
window.removeEventListener('click', clickHandler, { capture: true } as EventListenerOptions);
|
||||||
|
}
|
||||||
|
cleanupScript();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ClientOnly>
|
||||||
|
<span class="hidden" />
|
||||||
|
</ClientOnly>
|
||||||
|
</template>
|
||||||
@@ -1,3 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<router-view/>
|
<ClientOnly>
|
||||||
|
<AppTopLoadingBar />
|
||||||
|
<OfflineOverlay />
|
||||||
|
</ClientOnly>
|
||||||
|
<router-view />
|
||||||
</template>
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import ClientOnly from '@/components/ClientOnly';
|
||||||
|
import AppTopLoadingBar from '@/components/AppTopLoadingBar.vue'
|
||||||
|
import OfflineOverlay from '@/components/OfflineOverlay.vue'
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { cn } from '@/lib/utils';
|
|
||||||
import { computed } from 'vue';
|
|
||||||
|
|
||||||
type Variant = 'primary' | 'secondary' | 'danger' | 'ghost';
|
|
||||||
type Size = 'sm' | 'md';
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
|
||||||
variant?: Variant;
|
|
||||||
size?: Size;
|
|
||||||
loading?: boolean;
|
|
||||||
disabled?: boolean;
|
|
||||||
type?: 'button' | 'submit' | 'reset';
|
|
||||||
}>(), {
|
|
||||||
variant: 'primary',
|
|
||||||
size: 'md',
|
|
||||||
loading: false,
|
|
||||||
disabled: false,
|
|
||||||
type: 'button',
|
|
||||||
});
|
|
||||||
|
|
||||||
const baseClass = 'inline-flex items-center justify-center gap-2 rounded-md font-medium transition-all press-animated select-none';
|
|
||||||
|
|
||||||
const sizeClass = computed(() => {
|
|
||||||
switch (props.size) {
|
|
||||||
case 'sm':
|
|
||||||
return 'px-3 py-1.5 text-sm';
|
|
||||||
case 'md':
|
|
||||||
default:
|
|
||||||
return 'px-4 py-2 text-sm';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const variantClass = computed(() => {
|
|
||||||
switch (props.variant) {
|
|
||||||
case 'secondary':
|
|
||||||
return 'bg-muted/50 text-foreground hover:bg-muted border border-border';
|
|
||||||
case 'danger':
|
|
||||||
return 'bg-danger text-white hover:bg-danger/90';
|
|
||||||
case 'ghost':
|
|
||||||
return 'bg-transparent text-foreground/70 hover:text-foreground hover:bg-muted/50';
|
|
||||||
case 'primary':
|
|
||||||
default:
|
|
||||||
return 'bg-primary text-white hover:bg-primary/90';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const disabledClass = computed(() => (props.disabled || props.loading) ? 'opacity-60 cursor-not-allowed' : '');
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<button
|
|
||||||
:type="type"
|
|
||||||
:disabled="disabled || loading"
|
|
||||||
:class="cn(baseClass, sizeClass, variantClass, disabledClass)"
|
|
||||||
>
|
|
||||||
<span v-if="loading" class="inline-flex items-center" aria-hidden="true">
|
|
||||||
<svg class="w-4 h-4 animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
||||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
|
|
||||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 0 1 8-8v4a4 4 0 0 0-4 4H4z" />
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
<slot name="icon" />
|
|
||||||
<slot />
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import XIcon from '@/components/icons/XIcon.vue';
|
|
||||||
import { cn } from '@/lib/utils';
|
|
||||||
import { onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
|
||||||
|
|
||||||
// Ensure client-side only rendering to avoid hydration mismatch
|
|
||||||
const isMounted = ref(false);
|
|
||||||
onMounted(() => {
|
|
||||||
isMounted.value = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
|
||||||
visible: boolean;
|
|
||||||
title?: string;
|
|
||||||
closable?: boolean;
|
|
||||||
maxWidthClass?: string;
|
|
||||||
}>(), {
|
|
||||||
title: '',
|
|
||||||
closable: true,
|
|
||||||
maxWidthClass: 'max-w-lg',
|
|
||||||
});
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(e: 'update:visible', value: boolean): void;
|
|
||||||
(e: 'close'): void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const close = () => {
|
|
||||||
emit('update:visible', false);
|
|
||||||
emit('close');
|
|
||||||
};
|
|
||||||
|
|
||||||
const onKeydown = (e: KeyboardEvent) => {
|
|
||||||
if (!props.visible) return;
|
|
||||||
if (!props.closable) return;
|
|
||||||
if (e.key === 'Escape') close();
|
|
||||||
};
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.visible,
|
|
||||||
(v) => {
|
|
||||||
if (typeof window === 'undefined') return;
|
|
||||||
if (v) window.addEventListener('keydown', onKeydown);
|
|
||||||
else window.removeEventListener('keydown', onKeydown);
|
|
||||||
},
|
|
||||||
{ immediate: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
if (typeof window === 'undefined') return;
|
|
||||||
window.removeEventListener('keydown', onKeydown);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Teleport v-if="isMounted" to="body">
|
|
||||||
<Transition
|
|
||||||
enter-active-class="transition-all duration-200 ease-out"
|
|
||||||
enter-from-class="opacity-0"
|
|
||||||
enter-to-class="opacity-100"
|
|
||||||
leave-active-class="transition-all duration-150 ease-in"
|
|
||||||
leave-from-class="opacity-100"
|
|
||||||
leave-to-class="opacity-0"
|
|
||||||
>
|
|
||||||
<div v-if="visible" class="fixed inset-0 z-[9999]">
|
|
||||||
<!-- Backdrop -->
|
|
||||||
<div
|
|
||||||
class="absolute inset-0 bg-black/30"
|
|
||||||
@click="closable && close()"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Panel -->
|
|
||||||
<div class="absolute inset-0 flex items-center justify-center p-4">
|
|
||||||
<div :class="cn('w-full bg-surface border border-border rounded-lg shadow-lg overflow-hidden', maxWidthClass)">
|
|
||||||
<!-- Header slot -->
|
|
||||||
<div v-if="$slots.header" class="px-5 py-4 border-b border-border">
|
|
||||||
<slot name="header" :close="close" />
|
|
||||||
</div>
|
|
||||||
<!-- Default title -->
|
|
||||||
<div v-else-if="title" class="flex items-center justify-between gap-3 px-5 py-4 border-b border-border">
|
|
||||||
<h3 class="text-sm font-semibold text-foreground">
|
|
||||||
{{ title }}
|
|
||||||
</h3>
|
|
||||||
<button
|
|
||||||
v-if="closable"
|
|
||||||
type="button"
|
|
||||||
class="p-1 rounded-md text-foreground/60 hover:text-foreground hover:bg-muted/50 transition-all"
|
|
||||||
@click="close"
|
|
||||||
aria-label="Close"
|
|
||||||
>
|
|
||||||
<XIcon class="w-4 h-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Content -->
|
|
||||||
<div class="p-5">
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Footer slot -->
|
|
||||||
<div v-if="$slots.footer" class="px-5 py-4 border-t border-border bg-muted/20">
|
|
||||||
<slot name="footer" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Transition>
|
|
||||||
</Teleport>
|
|
||||||
</template>
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { cn } from '@/lib/utils';
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
|
||||||
modelValue: boolean;
|
|
||||||
disabled?: boolean;
|
|
||||||
ariaLabel?: string;
|
|
||||||
}>(), {
|
|
||||||
disabled: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(e: 'update:modelValue', value: boolean): void;
|
|
||||||
(e: 'change', value: boolean): void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const toggle = () => {
|
|
||||||
if (props.disabled) return;
|
|
||||||
const next = !props.modelValue;
|
|
||||||
emit('update:modelValue', next);
|
|
||||||
emit('change', next);
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
role="switch"
|
|
||||||
:aria-checked="modelValue"
|
|
||||||
:aria-label="ariaLabel"
|
|
||||||
:disabled="disabled"
|
|
||||||
@click="toggle"
|
|
||||||
:class="cn(
|
|
||||||
'relative inline-flex h-6 w-11 items-center rounded-full transition-colors',
|
|
||||||
disabled ? 'opacity-60 cursor-not-allowed' : 'cursor-pointer',
|
|
||||||
modelValue ? 'bg-primary' : 'bg-border'
|
|
||||||
)"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
:class="cn(
|
|
||||||
'inline-block h-5 w-5 transform rounded-full bg-white shadow-sm transition-transform',
|
|
||||||
modelValue ? 'translate-x-5' : 'translate-x-1'
|
|
||||||
)"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
@@ -60,7 +60,7 @@ const getButtonClass = (variant?: string) => {
|
|||||||
<!-- Title & Actions -->
|
<!-- Title & Actions -->
|
||||||
<div class="flex items-start justify-between gap-4 flex-wrap">
|
<div class="flex items-start justify-between gap-4 flex-wrap">
|
||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
<h1 v-if="typeof props.title == 'string'" class="text-3xl font-bold text-gray-900 mb-1">{{ title }}</h1>
|
<h1 v-if="typeof props.title == 'string'" class="text-2xl font-bold text-gray-900 mb-1">{{ title }}</h1>
|
||||||
<component v-else :is="title" />
|
<component v-else :is="title" />
|
||||||
<p v-if="description" class="text-gray-600">{{ description }}</p>
|
<p v-if="description" class="text-gray-600">{{ description }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useTranslation } from 'i18next-vue';
|
||||||
import { VNode } from 'vue';
|
import { VNode } from 'vue';
|
||||||
|
|
||||||
interface Trend {
|
interface Trend {
|
||||||
@@ -6,7 +7,7 @@ interface Trend {
|
|||||||
isPositive: boolean;
|
isPositive: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
export interface StatProps {
|
||||||
title: string;
|
title: string;
|
||||||
value: string | number;
|
value: string | number;
|
||||||
icon?: string | VNode;
|
icon?: string | VNode;
|
||||||
@@ -14,10 +15,12 @@ interface Props {
|
|||||||
color?: 'primary' | 'success' | 'warning' | 'danger' | 'info';
|
color?: 'primary' | 'success' | 'warning' | 'danger' | 'info';
|
||||||
}
|
}
|
||||||
|
|
||||||
withDefaults(defineProps<Props>(), {
|
withDefaults(defineProps<StatProps>(), {
|
||||||
color: 'primary'
|
color: 'primary'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// const gradients = {
|
// const gradients = {
|
||||||
// primary: 'from-primary/20 to-primary/5',
|
// primary: 'from-primary/20 to-primary/5',
|
||||||
// success: 'from-success/20 to-success/5',
|
// success: 'from-success/20 to-success/5',
|
||||||
@@ -37,7 +40,7 @@ const iconColors = {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="[
|
<div :class="[
|
||||||
'transform translate-y-0 relative overflow-hidden rounded-2xl p-6 bg-surface',
|
'transform translate-y-0 relative overflow-hidden rounded-2xl p-6 bg-header',
|
||||||
// gradients[color],
|
// gradients[color],
|
||||||
'border border-gray-300 transition-all duration-300',
|
'border border-gray-300 transition-all duration-300',
|
||||||
// 'group cursor-pointer'
|
// 'group cursor-pointer'
|
||||||
@@ -46,7 +49,7 @@ const iconColors = {
|
|||||||
<div class="relative z-10">
|
<div class="relative z-10">
|
||||||
<div class="flex items-start justify-between mb-3">
|
<div class="flex items-start justify-between mb-3">
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm font-medium text-gray-600 mb-1">{{ title }}</p>
|
<p class="text-sm font-medium text-gray-600 mb-1">{{ $t(title) }}</p>
|
||||||
<p class="text-3xl font-bold text-gray-900">{{ value }}</p>
|
<p class="text-3xl font-bold text-gray-900">{{ value }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -76,7 +79,7 @@ const iconColors = {
|
|||||||
</svg>
|
</svg>
|
||||||
{{ Math.abs(trend.value) }}%
|
{{ Math.abs(trend.value) }}%
|
||||||
</span>
|
</span>
|
||||||
<span class="text-gray-500">vs last month</span>
|
<span class="text-gray-500">{{ t('overview.stats.trendVsLastMonth') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,12 +6,12 @@
|
|||||||
fill="#a6acb9" />
|
fill="#a6acb9" />
|
||||||
<path
|
<path
|
||||||
d="M74 42c-18 0-32 14-32 32v320c0 18 14 32 32 32h320c18 0 32-14 32-32V74c0-18-14-32-32-32H74zM10 74c0-35 29-64 64-64h320c35 0 64 29 64 64v320c0 35-29 64-64 64H74c-35 0-64-29-64-64V74zm208 256v-80h-80c-9 0-16-7-16-16s7-16 16-16h80v-80c0-9 7-16 16-16s16 7 16 16v80h80c9 0 16 7 16 16s-7 16-16 16h-80v80c0 9-7 16-16 16s-16-7-16-16z"
|
d="M74 42c-18 0-32 14-32 32v320c0 18 14 32 32 32h320c18 0 32-14 32-32V74c0-18-14-32-32-32H74zM10 74c0-35 29-64 64-64h320c35 0 64 29 64 64v320c0 35-29 64-64 64H74c-35 0-64-29-64-64V74zm208 256v-80h-80c-9 0-16-7-16-16s7-16 16-16h80v-80c0-9 7-16 16-16s16 7 16 16v80h80c9 0 16 7 16 16s-7 16-16 16h-80v80c0 9-7 16-16 16s-16-7-16-16z"
|
||||||
fill="#1e3050" />
|
fill="currentColor" />
|
||||||
</svg>
|
</svg>
|
||||||
<svg v-else xmlns="http://www.w3.org/2000/svg" class="v-mid m-a" height="24" viewBox="-10 -226 468 468">
|
<svg v-else xmlns="http://www.w3.org/2000/svg" class="v-mid m-a" height="24" viewBox="-10 -226 468 468">
|
||||||
<path
|
<path
|
||||||
d="M64-184c-18 0-32 14-32 32v320c0 18 14 32 32 32h320c18 0 32-14 32-32v-320c0-18-14-32-32-32H64zM0-152c0-35 29-64 64-64h320c35 0 64 29 64 64v320c0 35-29 64-64 64H64c-35 0-64-29-64-64v-320zm208 256V24h-80c-9 0-16-7-16-16s7-16 16-16h80v-80c0-9 7-16 16-16s16 7 16 16v80h80c9 0 16 7 16 16s-7 16-16 16h-80v80c0 9-7 16-16 16s-16-7-16-16z"
|
d="M64-184c-18 0-32 14-32 32v320c0 18 14 32 32 32h320c18 0 32-14 32-32v-320c0-18-14-32-32-32H64zM0-152c0-35 29-64 64-64h320c35 0 64 29 64 64v320c0 35-29 64-64 64H64c-35 0-64-29-64-64v-320zm208 256V24h-80c-9 0-16-7-16-16s7-16 16-16h80v-80c0-9 7-16 16-16s16 7 16 16v80h80c9 0 16 7 16 16s-7 16-16 16h-80v80c0 9-7 16-16 16s-16-7-16-16z"
|
||||||
fill="#1e3050" />
|
fill="currentColor" />
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 518"><path d="M234 124v256c58 3 113 25 156 63l47 41c9 8 23 10 34 5 12-5 19-16 19-29V44c0-13-7-24-19-29-11-5-25-3-34 5l-47 41c-43 38-98 60-156 63z" fill="#a6acb9"/><path d="M138 124c-71 0-128 57-128 128s57 128 128 128v96c0 18 14 32 32 32h32c18 0 32-14 32-32V124h-96z" fill="#1e3050"/></svg>
|
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 518"><path d="M234 124v256c58 3 113 25 156 63l47 41c9 8 23 10 34 5 12-5 19-16 19-29V44c0-13-7-24-19-29-11-5-25-3-34 5l-47 41c-43 38-98 60-156 63z" fill="var(--fill1)"/><path d="M138 124c-71 0-128 57-128 128s57 128 128 128v96c0 18 14 32 32 32h32c18 0 32-14 32-32V124h-96z" fill="currentColor"/></svg>
|
||||||
<svg v-else xmlns="http://www.w3.org/2000/svg" width="500" height="518" viewBox="-10 -244 500 518"><path d="M461-229c12 5 19 16 19 29v416c0 13-7 24-19 29-11 5-25 3-34-5l-47-41c-43-38-98-60-156-63v96c0 18-14 32-32 32h-32c-18 0-32-14-32-32v-96C57 136 0 79 0 8s57-128 128-128h85c61 0 121-23 167-63l47-41c9-8 23-10 34-5zM224 72c70 3 138 29 192 74v-276c-54 45-122 71-192 74V72z" fill="currentColor"/></svg>
|
<svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="-10 -242 500 516"><path d="M448-194v404l-26-24c-50-47-114-75-182-81V-89c68-6 132-34 182-81l26-24zM240 137c60 6 116 31 160 72l34 32c5 4 12 7 19 7 15 0 27-12 27-27v-425c0-16-12-28-27-28-7 0-14 3-19 8l-34 31c-50 47-116 73-185 73h-87C57-120 0-63 0 8c0 60 41 110 96 124v84c0 27 22 48 48 48h48c27 0 48-21 48-48v-79zm-40-1h8v80c0 9-7 16-16 16h-48c-9 0-16-7-16-16v-80h72zm0-224h8v192h-80c-53 0-96-43-96-96s43-96 96-96h72z" fill="currentColor"/></svg>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
|||||||
@@ -5,9 +5,6 @@ defineProps<{
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 580 524"><path d="M10 448c0 36 30 66 67 66h427c36 0 66-30 66-66 0-12-3-23-8-33L353 47c-13-23-37-37-63-37s-50 14-63 37L19 415c-6 10-9 21-9 33zm301-46c0 12-9 21-21 21s-21-9-21-21 9-21 21-21 21 9 21 21zm-35-238c0-8 6-14 14-14s14 6 14 14v168c0 8-6 14-14 14s-14-6-14-14V164z" fill="color-mix(in srgb, currentColor 40%, transparent)"/><path d="M290 423c-12 0-21-9-21-21s9-21 21-21 21 9 21 21-9 21-21 21zm14-91c0 8-6 14-14 14s-14-6-14-14V164c0-8 6-14 14-14s14 6 14 14v168z" fill="currentColor"/></svg>
|
||||||
<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z" />
|
<svg v-else xmlns="http://www.w3.org/2000/svg" width="531" height="488" viewBox="-13 -224 531 488"><path d="M253-106c9 0 18 8 18 18V56c0 10-9 18-18 18-10 0-18-8-18-18V-88c0-10 8-18 18-18zm0 279c14 0 27-12 27-27s-13-27-27-27c-15 0-27 12-27 27s12 27 27 27zm-63-350c12-23 36-37 63-37 26 0 50 14 62 37l180 324c13 22 13 50 0 72s-37 35-62 35H73c-26 0-50-13-63-35s-13-50 0-72l180-324zm63-1c-14 0-26 7-32 19L41 165c-6 11-6 24 0 35 7 11 19 18 32 18h360c12 0 24-7 31-18 6-11 6-24 0-35L284-159c-6-12-18-19-31-19z" fill="currentColor"/></svg>
|
||||||
<path d="M12 9v4" />
|
|
||||||
<path d="M12 17h.01" />
|
|
||||||
</svg>
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 468 532"><path d="M10 391c0 19 16 35 36 35h376c20 0 36-16 36-35 0-9-3-16-8-23l-10-12c-30-37-46-84-46-132v-22c0-77-55-142-128-157v-3c0-18-14-32-32-32s-32 14-32 32v3C129 60 74 125 74 202v22c0 48-16 95-46 132l-10 12c-5 7-8 14-8 23z" fill="#a6acb9"/><path d="M172 474c7 28 32 48 62 48s55-20 62-48H172z" fill="#1e3050"/></svg>
|
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 468 532"><path d="M10 391c0 19 16 35 36 35h376c20 0 36-16 36-35 0-9-3-16-8-23l-10-12c-30-37-46-84-46-132v-22c0-77-55-142-128-157v-3c0-18-14-32-32-32s-32 14-32 32v3C129 60 74 125 74 202v22c0 48-16 95-46 132l-10 12c-5 7-8 14-8 23z" fill="var(--fill1)"/><path d="M172 474c7 28 32 48 62 48s55-20 62-48H172z" fill="currentColor"/></svg>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" v-else viewBox="-10 -258 468 532">
|
<svg xmlns="http://www.w3.org/2000/svg" v-else viewBox="-10 -258 468 532">
|
||||||
<path
|
<path
|
||||||
d="M224-248c-13 0-24 11-24 24v10C119-203 56-133 56-48v15C56 4 46 41 27 74L5 111c-3 6-5 13-5 19 0 21 17 38 38 38h372c21 0 38-17 38-38 0-6-2-13-5-19l-22-37c-19-33-29-70-29-108v-14c0-85-63-155-144-166v-10c0-13-11-24-24-24zm168 368H56l12-22c24-40 36-85 36-131v-15c0-66 54-120 120-120s120 54 120 120v15c0 46 12 91 36 131l12 22zm-236 96c10 28 37 48 68 48s58-20 68-48H156z"
|
d="M224-248c-13 0-24 11-24 24v10C119-203 56-133 56-48v15C56 4 46 41 27 74L5 111c-3 6-5 13-5 19 0 21 17 38 38 38h372c21 0 38-17 38-38 0-6-2-13-5-19l-22-37c-19-33-29-70-29-108v-14c0-85-63-155-144-166v-10c0-13-11-24-24-24zm168 368H56l12-22c24-40 36-85 36-131v-15c0-66 54-120 120-120s120 54 120 120v15c0 46 12 91 36 131l12 22zm-236 96c10 28 37 48 68 48s58-20 68-48H156z"
|
||||||
|
|||||||
7
src/components/icons/BellDot.vue
Normal file
7
src/components/icons/BellDot.vue
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" v-if="filled" viewBox="0 0 660 535"><path d="M106 394c0 19 16 35 36 35h376c20 0 36-16 36-35 0-9-3-16-8-23l-10-12c-30-37-46-84-46-132v-22c0-77-55-142-128-157v-3c0-18-14-32-32-32s-32 14-32 32v3c-73 15-128 80-128 157v22c0 48-16 95-46 132l-10 12c-5 7-8 14-8 23z" fill="var(--fill1)"/><path d="M616 28c5 12 0 26-12 31l-56 24c-13 5-27 0-32-13-5-12 0-26 13-31l56-24c12-5 26 0 31 13zM10 197c0-13 11-24 24-24h64c13 0 24 11 24 24s-11 24-24 24H34c-13 0-24-11-24-24zm258 280h124c-7 28-32 48-62 48s-55-20-62-48zm294-304h64c13 0 24 11 24 24s-11 24-24 24h-64c-13 0-24-11-24-24s11-24 24-24zM57 59c-13-5-18-19-13-31 5-13 19-18 32-13l56 24c12 5 17 19 12 31-5 13-19 18-31 13L57 59z" fill="var(--fill4)"/></svg>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" v-else width="660" height="535" viewBox="-10 -261 660 535"><path d="M606-233c-5-13-19-18-31-13l-56 24c-13 5-18 19-13 31 5 13 19 18 31 13l56-24c13-5 18-19 13-31zm-286-15c-13 0-24 11-24 24v10c-81 11-144 81-144 166v15c0 37-10 74-29 107l-22 37c-3 6-5 13-5 19 0 21 17 38 38 38h372c21 0 38-17 38-38 0-6-2-13-5-19l-22-37c-19-33-29-70-29-108v-14c0-85-63-155-144-166v-10c0-13-11-24-24-24zm168 368H152l12-22c24-40 36-85 36-131v-15c0-66 54-120 120-120s120 54 120 120v15c0 46 12 91 36 131l12 22zm-236 96c10 28 37 48 68 48s58-20 68-48H252zM0-64c0 13 11 24 24 24h64c13 0 24-11 24-24s-11-24-24-24H24C11-88 0-77 0-64zm552-24c-13 0-24 11-24 24s11 24 24 24h64c13 0 24-11 24-24s-11-24-24-24h-64zM47-202l56 24c12 5 26 0 31-12 5-13 0-27-13-32l-56-24c-12-5-26 0-31 13-5 12 0 26 13 31z" fill="currentColor"/></svg>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
defineProps<{ filled?: boolean }>();
|
||||||
|
</script>
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
defineProps<{
|
|
||||||
filled?: boolean;
|
|
||||||
}>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" />
|
|
||||||
<path d="M13.73 21a2 2 0 0 1-3.46 0" />
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
7
src/components/icons/BellOff.vue
Normal file
7
src/components/icons/BellOff.vue
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<template>
|
||||||
|
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 600"><path d="M76 425c0 19 16 35 36 35h246L140 242v16c0 48-16 94-46 132l-10 12c-5 7-8 14-8 22zm0 0zm162 83c7 28 32 48 62 48s55-20 62-48H238z" fill="var(--fill1)"/><path d="M19 19c9-9 25-9 34 0l120 120c23-30 56-52 95-60v-3c0-18 14-32 32-32s32 14 32 32v3c73 15 128 80 128 157v22c0 48 16 95 46 132l10 12c5 7 8 14 8 23 0 17-13 32-30 35l87 87c9 10 9 25 0 34s-25 9-34 0L19 53c-9-9-9-25 0-34z" fill="var(--fill4)"/></svg>
|
||||||
|
<svg v-else xmlns="http://www.w3.org/2000/svg" width="600" height="601" viewBox="-12 -292 600 601"><path d="M41-273c-9-9-25-9-34 0s-9 25 0 34l528 528c9 10 25 10 34 0 9-9 9-24 0-34l-88-88c18-3 31-18 31-37 0-6-2-13-5-19l-22-37c-19-33-29-70-29-108v-14c0-85-63-155-144-166v-10c0-13-11-24-24-24s-24 11-24 24v10c-42 6-79 27-105 59L41-273zm152 152c22-29 56-47 95-47 66 0 120 54 120 120v15c0 46 12 91 36 131l12 22h-22L193-121zM133 98c19-33 31-71 34-109l-47-47v25c0 37-10 74-29 107l-22 37c-3 6-5 13-5 19 0 21 17 38 38 38h244l-48-48H120l13-22zm87 118c10 28 37 48 68 48s58-20 68-48H220z" fill="currentColor"/></svg>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
defineProps<{ filled?: boolean }>();
|
||||||
|
</script>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" v-if="filled" viewBox="0 0 580 524"><path d="M10 234v112c0 46 38 84 84 84s84-38 84-84V234c0-46-38-84-84-84s-84 38-84 84zM206 94v252c0 46 38 84 84 84s84-38 84-84V94c0-46-38-84-84-84s-84 38-84 84zm196 56v196c0 46 38 84 84 84s84-38 84-84V150c0-46-38-84-84-84s-84 38-84 84z" fill="#a6acb9"/><path d="M10 500c0-8 6-14 14-14h532c8 0 14 6 14 14s-6 14-14 14H24c-8 0-14-6-14-14z" fill="#1e3050"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" v-if="filled" viewBox="0 0 600 564"><path d="M21 254c11 14 31 16 45 5l141-112 108 81c12 8 28 8 39-1l140-112v39c0 18 14 32 32 32s32-14 32-32V42c0-18-14-32-32-32H414c-18 0-32 14-32 32s14 32 32 32h29l-110 88-108-82c-11-8-28-8-39 1L26 209c-14 11-16 31-5 45zm25 108v96c0 18 14 32 32 32s32-14 32-32v-96c0-18-14-32-32-32s-32 14-32 32zm128-96v192c0 18 14 32 32 32s32-14 32-32V266c0-18-14-32-32-32s-32 14-32 32z" fill="var(--fill1)"/><path d="M446 554c80 0 144-64 144-144s-64-144-144-144-144 64-144 144 64 144 144 144zm0-240c9 0 16 7 16 16v8h16c9 0 16 7 16 16s-7 16-16 16h-46c-5 0-10 5-10 10s4 9 8 10l45 8c20 4 35 22 35 42 0 23-19 42-42 42h-6v8c0 9-7 16-16 16s-16-7-16-16v-8h-16c-9 0-16-7-16-16s7-16 16-16h54c5 0 10-4 10-10 0-5-4-9-8-10l-45-8c-20-4-35-21-35-42 0-22 18-41 40-42v-8c0-9 7-16 16-16z" fill="var(--fill4)"/></svg>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" v-else viewBox="-10 -226 532 468"><path d="M272-184c9 0 16 7 16 16v352c0 9-7 16-16 16h-32c-9 0-16-7-16-16v-352c0-9 7-16 16-16h32zm-32-32c-26 0-48 22-48 48v352c0 27 22 48 48 48h32c27 0 48-21 48-48v-352c0-26-21-48-48-48h-32zM80 8c9 0 16 7 16 16v160c0 9-7 16-16 16H48c-9 0-16-7-16-16V24c0-9 7-16 16-16h32zM48-24C22-24 0-2 0 24v160c0 27 22 48 48 48h32c27 0 48-21 48-48V24c0-26-21-48-48-48H48zm384-96h32c9 0 16 7 16 16v288c0 9-7 16-16 16h-32c-9 0-16-7-16-16v-288c0-9 7-16 16-16zm-48 16v288c0 27 22 48 48 48h32c27 0 48-21 48-48v-288c0-26-21-48-48-48h-32c-26 0-48 22-48 48z" fill="#1e3050"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" v-else viewBox="0 0 599 564"><path d="M421 10c-13 0-24 11-24 24s11 24 24 24h53L333 178 221 80c-8-7-20-8-29-2L24 190c-11 7-14 22-7 33s22 14 33 7l153-102 114 100c9 8 23 8 32 0L509 91v55c0 13 11 24 24 24s24-11 24-24V34c0-13-11-24-24-24H421zM205 234c-13 0-24 11-24 24v208c0 13 11 24 24 24s24-11 24-24V258c0-13-11-24-24-24zM69 330c-13 0-24 11-24 24v112c0 13 11 24 24 24s24-11 24-24V354c0-13-11-24-24-24zm376 224c80 0 144-64 144-144s-64-144-144-144-144 64-144 144 64 144 144 144zm0-240c9 0 16 7 16 16v8h16c9 0 16 7 16 16s-7 16-16 16h-46c-5 0-10 5-10 10s4 9 8 10l45 8c20 4 35 22 35 42 0 23-19 42-42 42h-6v8c0 9-7 16-16 16s-16-7-16-16v-8h-16c-9 0-16-7-16-16s7-16 16-16h54c5 0 10-4 10-10 0-5-4-9-8-10l-45-8c-20-4-35-21-35-42 0-22 18-41 40-42v-8c0-9 7-16 16-16z" fill="var(--fill4)"/></svg>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
defineProps<{ filled?: boolean }>();
|
defineProps<{ filled?: boolean }>();
|
||||||
|
|||||||
@@ -5,9 +5,6 @@ defineProps<{
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 532"><path d="M10 435c0 48 39 87 88 87h305c48 0 87-39 87-87 0-87-38-169-105-224l-48-41H164l-49 41C48 266 10 348 10 435zM138 36c0 4 1 8 3 12l37 74h144l37-74c2-4 3-8 3-12 0-14-12-26-26-26H164c-14 0-26 12-26 26zm44 275c0-29 23-53 52-53v-4c0-11 9-20 20-20s20 9 20 20v4h8c11 0 20 9 20 20s-9 20-20 20h-47c-7 0-13 6-13 13 0 6 4 11 10 12l42 7c25 4 44 26 44 52s-19 47-44 51v5c0 11-9 20-20 20s-20-9-20-20v-4h-24c-11 0-20-9-20-20s9-20 20-20h56c6 0 12-5 12-12 0-6-4-12-10-13l-42-7c-25-4-44-26-44-51z" fill="var(--fill1)"/><path d="M162 122c-13 0-24 11-24 24s11 24 24 24h176c13 0 24-11 24-24s-11-24-24-24H162zm92 112c-11 0-20 9-20 20v4c-29 0-52 24-52 53 0 25 19 47 44 51l42 7c6 1 10 7 10 13 0 7-6 12-12 12h-56c-11 0-20 9-20 20s9 20 20 20h24v4c0 11 9 20 20 20s20-9 20-20v-5c25-4 44-25 44-51s-18-48-44-52l-42-7c-6-1-10-6-10-13 0-6 6-12 13-12h47c11 0 20-9 20-20s-9-20-20-20h-8v-4c0-11-9-20-20-20z" fill="currentColor"/></svg>
|
||||||
<circle cx="12" cy="12" r="9"/>
|
<svg v-else xmlns="http://www.w3.org/2000/svg" width="500" height="532" viewBox="6 -258 500 532"><path d="m379-191-46 81c84 77 163 154 163 279 0 52-43 95-95 95H111c-52 0-95-43-95-95C16 44 96-33 179-110l-46-81c-3-6-5-12-5-19 0-21 17-38 38-38h180c21 0 38 17 38 38 0 7-2 13-5 19zM227-88l-1 1C134-4 64 61 64 169c0 26 21 47 47 47h290c26 0 47-21 47-47C448 61 378-4 286-87l-1-1h-58zm-7-48h72l37-64H183l37 64zm40 96c11 0 20 9 20 20v4h8c11 0 20 9 20 20s-9 20-20 20h-47c-7 0-13 6-13 13 0 6 4 11 10 12l42 7c25 4 44 26 44 52s-19 47-44 51v5c0 11-9 20-20 20s-20-9-20-20v-4h-24c-11 0-20-9-20-20s9-20 20-20h56c6 0 12-5 12-12 0-6-4-12-10-13l-42-7c-25-4-44-26-44-51 0-29 23-53 52-53v-4c0-11 9-20 20-20z" fill="currentColor"/></svg>
|
||||||
<path d="M12 16V8"/>
|
|
||||||
<path d="M9.5 10a2.5 2.5 0 0 1 5 0v4a2.5 2.5 0 0 1-5 0"/>
|
|
||||||
</svg>
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" v-if="filled" viewBox="0 0 532 404"><path d="M10 74c0-35 29-64 64-64h384c35 0 64 29 64 64v32H10V74zm0 96h512v160c0 35-29 64-64 64H74c-35 0-64-29-64-64V170zm64 136c0 13 11 24 24 24h48c13 0 24-11 24-24s-11-24-24-24H98c-13 0-24 11-24 24zm144 0c0 13 11 24 24 24h64c13 0 24-11 24-24s-11-24-24-24h-64c-13 0-24 11-24 24z" fill="#a6acb9"/><path d="M10 106h512v64H10zm0 0z" fill="#1e3050"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" v-if="filled" viewBox="0 0 532 404"><path d="M10 74c0-35 29-64 64-64h384c35 0 64 29 64 64v32H10V74zm0 96h512v160c0 35-29 64-64 64H74c-35 0-64-29-64-64V170zm64 136c0 13 11 24 24 24h48c13 0 24-11 24-24s-11-24-24-24H98c-13 0-24 11-24 24zm144 0c0 13 11 24 24 24h64c13 0 24-11 24-24s-11-24-24-24h-64c-13 0-24 11-24 24z" fill="var(--fill1)"/><path d="M10 106h512v64H10zm0 0z" fill="currentColor"/></svg>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" v-else viewBox="-10 -194 532 404"><path d="M448-136c9 0 16 7 16 16v32H48v-32c0-9 7-16 16-16h384zm16 112v160c0 9-7 16-16 16H64c-9 0-16-7-16-16V-24h416zM64-184c-35 0-64 29-64 64v256c0 35 29 64 64 64h384c35 0 64-29 64-64v-256c0-35-29-64-64-64H64zM80 96c0 13 11 24 24 24h48c13 0 24-11 24-24s-11-24-24-24h-48c-13 0-24 11-24 24zm144 0c0 13 11 24 24 24h64c13 0 24-11 24-24s-11-24-24-24h-64c-13 0-24 11-24 24z" fill="#1e3050"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" v-else viewBox="-10 -194 532 404"><path d="M448-136c9 0 16 7 16 16v32H48v-32c0-9 7-16 16-16h384zm16 112v160c0 9-7 16-16 16H64c-9 0-16-7-16-16V-24h416zM64-184c-35 0-64 29-64 64v256c0 35 29 64 64 64h384c35 0 64-29 64-64v-256c0-35-29-64-64-64H64zM80 96c0 13 11 24 24 24h48c13 0 24-11 24-24s-11-24-24-24h-48c-13 0-24 11-24 24zm144 0c0 13 11 24 24 24h64c13 0 24-11 24-24s-11-24-24-24h-64c-13 0-24 11-24 24z" fill="currentColor"/></svg>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
defineProps<{ filled?: boolean }>();
|
defineProps<{ filled?: boolean }>();
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
fill="#a6acb9" />
|
fill="#a6acb9" />
|
||||||
<path
|
<path
|
||||||
d="M208 26c3 0 7 0 10 1v103c0 31 25 56 56 56h103c1 3 1 7 1 11v261c0 27-21 48-48 48H74c-26 0-48-21-48-48V74c0-26 22-48 48-48h134zm156 137c2 2 4 4 6 7h-96c-22 0-40-18-40-40V34c3 2 5 4 7 6l123 123zM74 10c-35 0-64 29-64 64v384c0 35 29 64 64 64h256c35 0 64-29 64-64V197c0-17-7-34-19-46L253 29c-12-12-28-19-45-19H74zm144 272c9 0 16 7 16 16v96c0 9-7 16-16 16h-96c-9 0-16-7-16-16v-96c0-9 7-16 16-16h96zm-96-16c-18 0-32 14-32 32v96c0 18 14 32 32 32h96c18 0 32-14 32-32v-18l40 25c10 7 24-1 24-13v-84c0-12-14-20-24-13l-40 25v-18c0-18-14-32-32-32h-96zm176 38v84l-48-30v-24l48-30z"
|
d="M208 26c3 0 7 0 10 1v103c0 31 25 56 56 56h103c1 3 1 7 1 11v261c0 27-21 48-48 48H74c-26 0-48-21-48-48V74c0-26 22-48 48-48h134zm156 137c2 2 4 4 6 7h-96c-22 0-40-18-40-40V34c3 2 5 4 7 6l123 123zM74 10c-35 0-64 29-64 64v384c0 35 29 64 64 64h256c35 0 64-29 64-64V197c0-17-7-34-19-46L253 29c-12-12-28-19-45-19H74zm144 272c9 0 16 7 16 16v96c0 9-7 16-16 16h-96c-9 0-16-7-16-16v-96c0-9 7-16 16-16h96zm-96-16c-18 0-32 14-32 32v96c0 18 14 32 32 32h96c18 0 32-14 32-32v-18l40 25c10 7 24-1 24-13v-84c0-12-14-20-24-13l-40 25v-18c0-18-14-32-32-32h-96zm176 38v84l-48-30v-24l48-30z"
|
||||||
fill="#1e3050" />
|
fill="currentColor" />
|
||||||
</svg>
|
</svg>
|
||||||
<!-- Remote link icon -->
|
<!-- Remote link icon -->
|
||||||
<svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="0 0 596 564">
|
<svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="0 0 596 564">
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
fill="#a6acb9" />
|
fill="#a6acb9" />
|
||||||
<path
|
<path
|
||||||
d="M570 274H458c-2 118-55 195-99 230 116-14 207-111 211-230zM269 498c10 3 21 5 32 6-9-7-18-16-27-26l6-18c18 22 36 37 50 45 40-22 109-99 112-231H335l4-16h103c-3-132-72-209-112-231-39 22-107 96-112 224l-16 5c3-117 56-193 99-228C185 42 94 139 90 258h104l-55 16H90c0 5 1 10 1 14l-16 5c0-9-1-18-1-27C74 125 189 10 330 10s256 115 256 256-115 256-256 256c-23 0-45-3-66-9l5-15zm301-240c-4-119-95-216-211-230 44 35 97 112 99 230h112zM150 414l2 5 46 92 60-205-204 60 91 46 5 2zM31 373l-21-11 23-7 231-68 18-5-5 18-68 232-7 22-60-120-94 94-6 5-11-11 5-6 95-94-100-49z"
|
d="M570 274H458c-2 118-55 195-99 230 116-14 207-111 211-230zM269 498c10 3 21 5 32 6-9-7-18-16-27-26l6-18c18 22 36 37 50 45 40-22 109-99 112-231H335l4-16h103c-3-132-72-209-112-231-39 22-107 96-112 224l-16 5c3-117 56-193 99-228C185 42 94 139 90 258h104l-55 16H90c0 5 1 10 1 14l-16 5c0-9-1-18-1-27C74 125 189 10 330 10s256 115 256 256-115 256-256 256c-23 0-45-3-66-9l5-15zm301-240c-4-119-95-216-211-230 44 35 97 112 99 230h112zM150 414l2 5 46 92 60-205-204 60 91 46 5 2zM31 373l-21-11 23-7 231-68 18-5-5 18-68 232-7 22-60-120-94 94-6 5-11-11 5-6 95-94-100-49z"
|
||||||
fill="#1e3050" />
|
fill="currentColor" />
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|||||||
@@ -5,5 +5,6 @@ defineProps<{
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="524" height="524" viewBox="-10 -242 524 524"><path d="M252-232C113-232 0-119 0 20s113 252 252 252S504 159 504 20 391-232 252-232zM37 2c7-92 73-168 161-191-42 55-68 122-71 191H37zm0 36h89c4 69 30 136 71 191-87-23-153-98-160-191zm213 198c-50-52-83-125-87-198h179c-5 73-37 146-88 198h-4zM378 38h89c-7 92-73 168-161 191 42-55 68-122 71-191zm0 0zm0-36c-4-69-30-136-71-191 87 23 153 99 160 191h-89zM254-196c51 53 83 125 87 198H163c4-73 36-145 87-198h4z" fill="currentColor"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" v-if="filled" viewBox="0 0 627 563"><path d="M10 241h112c5-88 35-169 71-222C94 49 20 135 10 241zm0 48c10 106 84 193 183 222-36-52-66-134-71-222H10zm160-48h190c-4-62-22-121-45-165-13-25-27-44-38-56-6-5-10-8-12-10-2 2-6 5-12 10-11 12-25 31-38 56-23 44-41 103-45 165zm0 48c4 62 22 121 45 166 13 25 27 43 38 55 6 5 10 8 12 10 2-2 6-5 12-10 6-6 13-15 20-25-10-23-16-49-16-76 0-45 16-87 42-120H170zM337 19c34 50 64 126 70 210 21-8 43-12 66-12 15 0 30 2 44 5-16-97-87-175-180-203z" fill="var(--fill1)"/><path d="M473 553c80 0 144-64 144-144s-64-144-144-144-144 64-144 144 64 144 144 144zm87-145c-19-28-51-47-87-47s-68 19-87 47l-25-19c24-36 65-60 112-60s88 24 113 60l-26 19zm-23 17-26 19c-8-11-22-19-38-19s-30 8-38 19l-26-19c15-19 38-32 64-32s49 13 64 32zm-84 48c0-11 9-20 20-20s20 9 20 20-9 20-20 20-20-9-20-20z" fill="currentColor"/></svg>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" v-else viewBox="22 -258 628 564"><path d="M288 232c-1 0-7-1-18-12-12-11-24-28-36-49-22-39-39-92-41-147h160c17-19 38-35 62-46-7-76-37-145-70-187 81 22 144 87 162 169 12 1 23 3 34 5-21-121-126-213-253-213C147-248 32-133 32 8s115 256 256 256c17 0 33-2 49-5-10-14-18-30-23-47-3 3-5 6-7 8-12 11-18 12-19 12zM384-8H192c3-55 20-107 42-147 12-21 24-38 35-49 12-10 18-12 19-12s7 2 18 12c12 11 24 28 36 49 22 40 39 92 41 147zm0 0zM160-8H65c6-97 75-177 166-201-35 44-67 120-71 201zM65 24h95c4 82 36 157 71 201C140 201 71 121 65 24zm431 16c62 0 112 50 112 112s-50 112-112 112-112-50-112-112S434 40 496 40zm0 256c80 0 144-64 144-144S576 8 496 8 352 72 352 152s64 144 144 144zm96-165c-24-26-58-43-96-43s-72 17-96 43l25 21c17-20 43-32 71-32s54 12 71 32l25-21zm-96 13c-21 0-41 8-55 22l25 21c8-7 19-11 30-11 12 0 22 4 30 11l25-21c-14-14-34-22-55-22zm0 92c11 0 20-9 20-20s-9-20-20-20-20 9-20 20 9 20 20 20z" fill="currentColor"/></svg>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 468 503"><path d="M10 397v32c0 35 29 64 64 64h320c35 0 64-29 64-64v-32c0-35-29-64-64-64H266v32c0 18-14 32-32 32s-32-14-32-32v-32H74c-35 0-64 29-64 64zm392 16c0 13-11 24-24 24s-24-11-24-24 11-24 24-24 24 11 24 24z" fill="#a6acb9"/><path d="M234 397c18 0 32-14 32-32V122l41 42c13 12 33 12 46 0 12-13 12-33 0-46l-96-96c-13-12-33-12-46 0l-96 96c-12 13-12 33 0 46 13 12 33 12 46 0l41-42v243c0 18 14 32 32 32z" fill="#1e3050"/></svg>
|
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 468 503"><path d="M10 397v32c0 35 29 64 64 64h320c35 0 64-29 64-64v-32c0-35-29-64-64-64H266v32c0 18-14 32-32 32s-32-14-32-32v-32H74c-35 0-64 29-64 64zm392 16c0 13-11 24-24 24s-24-11-24-24 11-24 24-24 24 11 24 24z" fill="#a6acb9"/><path d="M234 397c18 0 32-14 32-32V122l41 42c13 12 33 12 46 0 12-13 12-33 0-46l-96-96c-13-12-33-12-46 0l-96 96c-12 13-12 33 0 46 13 12 33 12 46 0l41-42v243c0 18 14 32 32 32z" fill="currentColor"/></svg>
|
||||||
<svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="-10 -260 468 502"><path d="M248 80c0 13-11 24-24 24s-24-11-24-24v-246l-63 63c-9 9-25 9-34 0s-9-25 0-34l104-104c9-9 25-9 34 0l104 104c9 9 9 25 0 34s-25 9-34 0l-63-63V80zm-96-8H64c-9 0-16 7-16 16v80c0 9 7 16 16 16h320c9 0 16-7 16-16V88c0-9-7-16-16-16h-88V24h88c35 0 64 29 64 64v80c0 35-29 64-64 64H64c-35 0-64-29-64-64V88c0-35 29-64 64-64h88v48zm168 56c0-13 11-24 24-24s24 11 24 24-11 24-24 24-24-11-24-24z" fill="#1e3050"/></svg>
|
<svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="-10 -260 468 502"><path d="M248 80c0 13-11 24-24 24s-24-11-24-24v-246l-63 63c-9 9-25 9-34 0s-9-25 0-34l104-104c9-9 25-9 34 0l104 104c9 9 9 25 0 34s-25 9-34 0l-63-63V80zm-96-8H64c-9 0-16 7-16 16v80c0 9 7 16 16 16h320c9 0 16-7 16-16V88c0-9-7-16-16-16h-88V24h88c35 0 64 29 64 64v80c0 35-29 64-64 64H64c-35 0-64-29-64-64V88c0-35 29-64 64-64h88v48zm168 56c0-13 11-24 24-24s24 11 24 24-11 24-24 24-24-11-24-24z" fill="currentColor"/></svg>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
defineProps<{ filled?: boolean }>();
|
defineProps<{ filled?: boolean }>();
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" v-if="filled" viewBox="0 0 539 535">
|
<svg xmlns="http://www.w3.org/2000/svg" v-if="filled" viewBox="0 0 539 535">
|
||||||
<path d="M61 281c2-1 4-3 6-5L269 89l202 187c2 2 4 4 6 5v180c0 35-29 64-64 64H125c-35 0-64-29-64-64V281z"
|
<path d="M61 281c2-1 4-3 6-5L269 89l202 187c2 2 4 4 6 5v180c0 35-29 64-64 64H125c-35 0-64-29-64-64V281z"
|
||||||
fill="#a6acb9" />
|
fill="var(--fill1)" />
|
||||||
<path
|
<path
|
||||||
d="M247 22c13-12 32-12 44 0l224 208c13 12 13 32 1 45s-32 14-45 2L269 89 67 276c-13 12-33 12-45-1s-12-33 1-45L247 22z"
|
d="M247 22c13-12 32-12 44 0l224 208c13 12 13 32 1 45s-32 14-45 2L269 89 67 276c-13 12-33 12-45-1s-12-33 1-45L247 22z"
|
||||||
fill="#1e3050" />
|
fill="currentColor" />
|
||||||
</svg>
|
</svg>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" v-else viewBox="-11 -259 535 533">
|
<svg xmlns="http://www.w3.org/2000/svg" v-else viewBox="-11 -259 535 533">
|
||||||
<path
|
<path
|
||||||
d="M272-242c-9-8-23-8-32 0L8-34C-2-25-3-10 6 0s24 11 34 2l8-7v205c0 35 29 64 64 64h288c35 0 64-29 64-64V-5l8 7c10 9 25 8 34-2s8-25-2-34L272-242zM416-48v248c0 9-7 16-16 16H112c-9 0-16-7-16-16V-48l160-144L416-48z"
|
d="M272-242c-9-8-23-8-32 0L8-34C-2-25-3-10 6 0s24 11 34 2l8-7v205c0 35 29 64 64 64h288c35 0 64-29 64-64V-5l8 7c10 9 25 8 34-2s8-25-2-34L272-242zM416-48v248c0 9-7 16-16 16H112c-9 0-16-7-16-16V-48l160-144L416-48z"
|
||||||
fill="#1e3050" />
|
fill="currentColor" />
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|||||||
7
src/components/icons/Inbox.vue
Normal file
7
src/components/icons/Inbox.vue
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" v-if="filled" viewBox="0 0 532 468"><path d="M10 268v126c0 35 29 64 64 64h384c35 0 64-29 64-64V268c0-3 0-6-1-9L494 65c-5-32-32-55-64-55H102c-32 0-59 23-64 55L11 259c-1 3-1 6-1 9zm64-2 28-192h328l28 192h-60c-12 0-23 7-29 18l-14 28c-6 11-17 18-29 18H206c-12 0-23-7-29-18l-14-28c-5-11-17-18-29-18H74z" fill="var(--fill1)"/><path d="M249 291c9 9 25 9 34 0l64-64c9-9 9-25 0-34s-25-9-34 0l-23 23v-86c0-13-11-24-24-24s-24 11-24 24v86l-23-23c-9-9-25-9-34 0-9 10-9 25 0 34l64 64z" fill="var(--fill4)"/></svg>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" v-else width="532" height="468" viewBox="-10 -226 532 468"><path d="M98-184c-16 0-30 12-32 27L35 56h100c11 0 21 5 27 14l23 34h142l23-34c6-9 16-14 27-14h100l-31-213c-2-15-16-27-31-27H98zM32 168c0 18 14 32 32 32h384c18 0 32-14 32-32V88H377l-23 34c-6 9-16 14-26 14H185c-11 0-21-5-27-14l-23-34H32v80zm2-329c5-32 32-55 64-55h317c31 0 58 23 63 55l33 227c1 3 1 6 1 10v92c0 35-29 64-64 64H64c-35 0-64-29-64-64V76c0-4 0-7 1-10l33-227zM339-21l-72 72c-6 7-16 7-22 0l-72-72c-6-6-6-16 0-22s16-6 22 0l45 44v-121c0-9 7-16 16-16s16 7 16 16V1l45-44c6-6 16-6 22 0 7 6 7 16 0 22z" fill="currentColor"/></svg>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
defineProps<{ filled?: boolean }>();
|
||||||
|
</script>
|
||||||
7
src/components/icons/LanguageIcon.vue
Normal file
7
src/components/icons/LanguageIcon.vue
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<template>
|
||||||
|
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 536"><path d="M269 477c58-131 80-180 128-288 5-12 16-19 29-19s24 7 29 19l128 288c7 16 0 35-16 42s-35 0-42-16l-20-45H347l-20 45c-7 16-26 23-42 16s-23-26-16-42zm107-83h100l-50-113-50 113z" fill="var(--fill1)"/><path d="M170 10c18 0 32 14 32 32v32h128c18 0 32 14 32 32s-14 32-32 32h-10l-8 23c-16 45-41 87-72 122 14 9 29 17 44 24l51 22-26 59-51-23c-23-10-45-22-66-36-21 17-44 32-69 44l-35 18c-15 8-35 1-43-15-7-15-1-35 15-43l34-17c17-8 32-18 47-28-14-13-27-27-39-41l-21-24c-11-14-9-34 5-46 13-11 33-9 45 5l20 24c11 14 24 27 37 39 28-31 50-66 64-106v-1H42c-18 0-32-14-32-32s14-32 32-32h96V42c0-18 14-32 32-32z" fill="var(--fill3)"/></svg>
|
||||||
|
<svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="0 0 599 535"><path d="M178 10c13 0 24 11 24 24v56h136c13 0 24 11 24 24s-11 24-24 24h-16l-17 38c-18 44-45 83-78 116 14 10 29 18 44 25l61 28 72-161c4-8 13-14 22-14 10 0 18 6 22 14l136 304c5 12 0 27-12 32s-26 0-32-12l-29-66H341l-29 66c-5 12-20 17-32 12s-17-20-12-32l45-99-61-29c-22-9-42-21-61-35-18 14-37 26-57 36l-57 30c-12 7-26 2-32-10-6-11-2-26 10-32l57-30c14-7 27-16 40-25-27-26-51-55-70-88-6-11-3-26 9-33 11-6 26-2 33 9 17 30 39 58 65 81 31-30 55-66 72-106l9-19H34c-13 0-24-11-24-24s11-24 24-24h120V34c0-13 11-24 24-24zm311 384-63-141-63 141h126z" fill="currentColor"/></svg>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
defineProps<{ filled?: boolean }>();
|
||||||
|
</script>
|
||||||
@@ -6,12 +6,12 @@
|
|||||||
fill="#a6acb9" />
|
fill="#a6acb9" />
|
||||||
<path
|
<path
|
||||||
d="M394 42c18 0 32 14 32 32v64H42V74c0-18 14-32 32-32h320zM42 394V170h96v256H74c-18 0-32-14-32-32zm128 32V170h256v224c0 18-14 32-32 32H170zM74 10c-35 0-64 29-64 64v320c0 35 29 64 64 64h320c35 0 64-29 64-64V74c0-35-29-64-64-64H74z"
|
d="M394 42c18 0 32 14 32 32v64H42V74c0-18 14-32 32-32h320zM42 394V170h96v256H74c-18 0-32-14-32-32zm128 32V170h256v224c0 18-14 32-32 32H170zM74 10c-35 0-64 29-64 64v320c0 35 29 64 64 64h320c35 0 64-29 64-64V74c0-35-29-64-64-64H74z"
|
||||||
fill="#1e3050" />
|
fill="currentColor" />
|
||||||
</svg>
|
</svg>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" v-else class="v-mid m-a" height="24" width="24" viewBox="-10 -226 468 468">
|
<svg xmlns="http://www.w3.org/2000/svg" v-else class="v-mid m-a" height="24" width="24" viewBox="-10 -226 468 468">
|
||||||
<path
|
<path
|
||||||
d="M384-184c18 0 32 14 32 32v64H32v-64c0-18 14-32 32-32h320zM32 168V-56h96v256H64c-18 0-32-14-32-32zm128 32V-56h256v224c0 18-14 32-32 32H160zM64-216c-35 0-64 29-64 64v320c0 35 29 64 64 64h320c35 0 64-29 64-64v-320c0-35-29-64-64-64H64z"
|
d="M384-184c18 0 32 14 32 32v64H32v-64c0-18 14-32 32-32h320zM32 168V-56h96v256H64c-18 0-32-14-32-32zm128 32V-56h256v224c0 18-14 32-32 32H160zM64-216c-35 0-64 29-64 64v320c0 35 29 64 64 64h320c35 0 64-29 64-64v-320c0-35-29-64-64-64H64z"
|
||||||
fill="#1e3050" />
|
fill="currentColor" />
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 596 404"><path d="M74 170v64c0 53 43 96 96 96h96v64h64v-64h96c53 0 96-43 96-96v-64c0-53-43-96-96-96h-96V10h-64v64h-96c-53 0-96 43-96 96zm96 0h256v64H170v-64z" fill="#a6acb9"/><path d="M170 10C82 10 10 82 10 170v64c0 88 72 160 160 160h96v-64h-96c-53 0-96-43-96-96v-64c0-53 43-96 96-96h96V10h-96zm256 384c88 0 160-72 160-160v-64c0-88-72-160-160-160h-96v64h96c53 0 96 43 96 96v64c0 53-43 96-96 96h-96v64h96zM202 170h-32v64h256v-64H202z" fill="#1e3050"/></svg>
|
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 596 404"><path d="M74 170v64c0 53 43 96 96 96h96v64h64v-64h96c53 0 96-43 96-96v-64c0-53-43-96-96-96h-96V10h-64v64h-96c-53 0-96 43-96 96zm96 0h256v64H170v-64z" fill="#a6acb9"/><path d="M170 10C82 10 10 82 10 170v64c0 88 72 160 160 160h96v-64h-96c-53 0-96-43-96-96v-64c0-53 43-96 96-96h96V10h-96zm256 384c88 0 160-72 160-160v-64c0-88-72-160-160-160h-96v64h96c53 0 96 43 96 96v64c0 53-43 96-96 96h-96v64h96zM202 170h-32v64h256v-64H202z" fill="currentColor"/></svg>
|
||||||
<svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="-10 -194 596 404"><path d="M160-184C72-184 0-112 0-24v64c0 88 72 160 160 160h96v-64h-96c-53 0-96-43-96-96v-64c0-53 43-96 96-96h96v-64h-96zm256 384c88 0 160-72 160-160v-64c0-88-72-160-160-160h-96v64h96c53 0 96 43 96 96v64c0 53-43 96-96 96h-96v64h96zM192-24h-32v64h256v-64H192z" fill="currentColor"/></svg>
|
<svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="-10 -194 596 404"><path d="M160-184C72-184 0-112 0-24v64c0 88 72 160 160 160h96v-64h-96c-53 0-96-43-96-96v-64c0-53 43-96 96-96h96v-64h-96zm256 384c88 0 160-72 160-160v-64c0-88-72-160-160-160h-96v64h96c53 0 96 43 96 96v64c0 53-43 96-96 96h-96v64h96zM192-24h-32v64h256v-64H192z" fill="currentColor"/></svg>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|||||||
7
src/components/icons/ListIcon.vue
Normal file
7
src/components/icons/ListIcon.vue
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<template>
|
||||||
|
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 468 468"><path d="M10 170v224c0 35 29 64 64 64h320c35 0 64-29 64-64V170H10zm96 88c0-13 11-24 24-24h208c13 0 24 11 24 24s-11 24-24 24H130c-13 0-24-11-24-24zm0 112c0-13 11-24 24-24h208c13 0 24 11 24 24s-11 24-24 24H130c-13 0-24-11-24-24z" fill="var(--fill1)"/><path d="M74 10c-35 0-64 29-64 64v96h448V74c0-35-29-64-64-64H74zm240 48h64c7 0 12 4 15 10 2 6 1 13-4 17l-32 32c-6 7-16 7-22 0l-32-32c-5-4-6-11-4-17 3-6 9-10 15-10z" fill="currentColor"/></svg>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" v-else viewBox="-10 -226 468 468"><path d="M64-184h320c18 0 32 14 32 32v64H32v-64c0-18 14-32 32-32zM32-56h384v224c0 18-14 32-32 32H64c-18 0-32-14-32-32V-56zM0-152v320c0 35 29 64 64 64h320c35 0 64-29 64-64v-320c0-35-29-64-64-64H64c-35 0-64 29-64 64zM112 8c-9 0-16 7-16 16s7 16 16 16h224c9 0 16-7 16-16s-7-16-16-16H112zm0 96c-9 0-16 7-16 16s7 16 16 16h224c9 0 16-7 16-16s-7-16-16-16H112zm200-260c-5 0-9 3-11 7-2 5-1 10 3 14l24 24c4 4 12 4 17 0l24-24c3-4 4-9 2-14-2-4-6-7-11-7h-48z" fill="currentColor"/></svg>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
defineProps<{ filled?: boolean }>();
|
||||||
|
</script>
|
||||||
@@ -5,7 +5,8 @@ defineProps<{
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" viewBox="0 0 532 404"><path d="M10 58c0 15 7 29 19 38l208 156c17 13 41 13 58 0L503 96c12-9 19-23 19-38v272c0 35-29 64-64 64H74c-35 0-64-29-64-64V58z" fill="var(--fill1)"/><path d="M58 10c-26 0-48 22-48 48 0 15 7 29 19 38l208 156c17 13 41 13 58 0L503 96c12-9 19-23 19-38 0-26-21-48-48-48H58z" fill="var(--fill4)"/></svg>
|
||||||
|
<svg v-else xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<rect width="20" height="16" x="2" y="4" rx="2" />
|
<rect width="20" height="16" x="2" y="4" rx="2" />
|
||||||
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" />
|
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
10
src/components/icons/MoneyCheck.vue
Normal file
10
src/components/icons/MoneyCheck.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
defineProps<{
|
||||||
|
filled?: boolean;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 610 500"><path d="M10 74c0-35 29-64 64-64h384c35 0 64 29 64 64v73c-21 3-41 13-58 29l-58 58H306c-13 0-24 11-24 24s11 24 24 24h52l-64 64c-13 14-23 30-28 48H74c-35 0-64-29-64-64V74zm76 93c0 25 19 47 44 51l42 7c6 1 10 7 10 13 0 7-5 12-12 12h-56c-11 0-20 9-20 20s9 20 20 20h24v4c0 11 9 20 20 20s20-9 20-20v-5c25-4 44-25 44-51s-18-48-44-52l-41-7c-6-1-11-6-11-12 0-7 6-13 13-13h47c11 0 20-9 20-20s-9-20-20-20h-8v-4c0-11-9-20-20-20s-20 9-20 20v4c-29 0-52 24-52 53zm196-21c0 13 11 24 24 24h128c13 0 24-11 24-24s-11-24-24-24H306c-13 0-24 11-24 24z" fill="var(--fill1)"/><path d="m298 473 12-60c3-12 9-24 18-33l119-119 80 80-119 119c-9 9-20 15-33 18l-59 12h-3c-8 0-15-7-15-15v-3zm0 0zm251-154-80-80 29-29c22-22 58-22 80 0s22 58 0 80l-29 29z" fill="currentColor"/></svg>
|
||||||
|
<svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="0 0 607 500"><path d="M74 42h384c18 0 32 14 32 32v81c11-3 22-5 32-5V74c0-35-28-64-64-64H74c-35 0-64 29-64 64v256c0 35 29 64 64 64h189l1-4c1-9 4-19 8-28H74c-17 0-32-14-32-32V74c0-18 15-32 32-32zm240 192c-8 0-16 7-16 16s8 16 16 16h44l32-32h-76zm-16-80c0 9 8 16 16 16h96c9 0 16-7 16-16s-7-16-16-16h-96c-8 0-16 7-16 16zM170 98c-8 0-16 7-16 16v8h-1c-26 0-47 21-47 46 0 23 17 42 39 46l45 8c7 1 12 7 12 14 0 8-6 14-14 14h-58c-8 0-16 7-16 16s8 16 16 16h24v8c0 9 8 16 16 16 9 0 16-7 16-16v-8h2c26 0 46-21 46-46 0-23-16-42-38-46l-46-8c-6-1-12-7-12-14 0-8 7-14 15-14h49c9 0 16-7 16-16s-7-16-16-16h-16v-8c0-9-7-16-16-16zm182 288 102-102 50 51-102 102c-4 5-11 8-17 9l-51 8 9-51c1-6 4-12 8-17zm0 0zm124-125 21-20c14-14 37-14 51 0s14 36 0 50l-21 21-51-51zM311 398l-12 75c0 1-1 1-1 2 0 8 7 15 15 15h3l74-13c13-2 26-8 35-17l145-146c27-26 27-69 0-96-26-26-69-26-96 0L329 364c-9 9-16 21-18 34z" fill="currentColor"/></svg>
|
||||||
|
</template>
|
||||||
@@ -1,13 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"
|
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 534 533"><path d="m303 93 104 104 34 34 62-62c14-13 21-32 21-51s-7-38-21-51l-36-36c-13-13-32-21-51-21s-38 8-51 21l-62 62z" fill="#a6acb9"/><path d="m95 377-24 87 86-25c7-1 13-5 18-10l-71-69c-4 5-8 10-9 17zm174-250 34-34 104 104 34 34-34 34-198 198c-11 11-24 19-39 23L42 521c-8 2-17 0-23-6s-9-15-6-23l35-128c5-15 12-28 23-39l198-198z" fill="#1e3050"/></svg>
|
||||||
stroke="none">
|
<svg v-else xmlns="http://www.w3.org/2000/svg" width="534" height="533" viewBox="-12 -258 534 533"><path d="M404-216c-11 0-21 4-28 12l-59 58 93 93 58-58c8-8 12-18 12-29s-4-21-12-28l-35-36c-8-8-18-12-29-12zM93 78l93 93L387-30l-93-93L93 78zm-21 25c-2 3-4 7-5 11L36 229l114-32c4-1 8-3 11-5l-89-89zm281-330c13-13 32-21 51-21s38 8 51 21l36 36c13 13 21 32 21 51s-8 38-21 51L197 205c-11 11-24 19-39 23L30 263c-8 2-17 0-23-6s-9-15-6-23l35-128c5-15 12-28 23-39l294-294z" fill="currentColor"/></svg>
|
||||||
<path
|
|
||||||
d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34a.9959.9959 0 0 0-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z" />
|
|
||||||
</svg>
|
|
||||||
<svg v-else xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
|
||||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"></path>
|
|
||||||
</svg>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ defineProps<{
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 628 628"><path d="M286 343 618 10 394 618 286 343z" fill="var(--fill1)"/><path d="M618 10 10 234l276 109L618 10z" fill="var(--fill4)"/></svg>
|
||||||
|
<svg v-else xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path d="m22 2-7 20-4-9-9-4Z" />
|
<path d="m22 2-7 20-4-9-9-4Z" />
|
||||||
<path d="M22 2 11 13" />
|
<path d="M22 2 11 13" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 567 580"><path d="M18 190c-8 14-6 32 5 43l37 36v42l-37 36c-11 12-13 29-5 43l46 80c8 14 24 21 40 17l50-14c11 8 23 15 36 21l13 50c4 15 18 26 34 26h93c16 0 30-11 34-26l13-50c13-6 25-13 36-21l50 14c15 4 32-3 40-17l46-80c8-14 6-31-6-43l-37-36c1-7 1-14 1-21s0-14-1-21l37-36c12-11 14-29 6-43l-46-80c-8-14-24-21-40-17l-50 14c-11-8-23-15-36-21l-13-50c-4-15-18-26-34-26h-93c-16 0-30 11-34 26l-13 50c-13 6-25 13-36 21l-50-13c-16-5-32 2-40 16l-46 80zm377 100c1 41-20 79-55 99-35 21-79 21-114 0-35-20-56-58-54-99-2-41 19-79 54-99 35-21 79-21 114 0 35 20 56 58 55 99zm-195 0c-2 31 14 59 40 75 27 15 59 15 86 0 26-16 42-44 41-75 1-31-15-59-41-75-27-15-59-15-86 0-26 16-42 44-40 75z" fill="#a6acb9"/><path d="M283 206c46 0 84 37 84 84 0 46-37 84-83 84-47 0-85-37-85-84 0-46 37-84 84-84zm1 196c61 0 111-51 111-112 0-62-51-112-112-112-62 0-112 51-112 112 0 62 51 112 113 112z" fill="#1e3050"/></svg>
|
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 567 580"><path d="M18 190c-8 14-6 32 5 43l37 36v42l-37 36c-11 12-13 29-5 43l46 80c8 14 24 21 40 17l50-14c11 8 23 15 36 21l13 50c4 15 18 26 34 26h93c16 0 30-11 34-26l13-50c13-6 25-13 36-21l50 14c15 4 32-3 40-17l46-80c8-14 6-31-6-43l-37-36c1-7 1-14 1-21s0-14-1-21l37-36c12-11 14-29 6-43l-46-80c-8-14-24-21-40-17l-50 14c-11-8-23-15-36-21l-13-50c-4-15-18-26-34-26h-93c-16 0-30 11-34 26l-13 50c-13 6-25 13-36 21l-50-13c-16-5-32 2-40 16l-46 80zm377 100c1 41-20 79-55 99-35 21-79 21-114 0-35-20-56-58-54-99-2-41 19-79 54-99 35-21 79-21 114 0 35 20 56 58 55 99zm-195 0c-2 31 14 59 40 75 27 15 59 15 86 0 26-16 42-44 41-75 1-31-15-59-41-75-27-15-59-15-86 0-26 16-42 44-40 75z" fill="var(--fill1)"/><path d="M283 206c46 0 84 37 84 84 0 46-37 84-83 84-47 0-85-37-85-84 0-46 37-84 84-84zm1 196c61 0 111-51 111-112 0-62-51-112-112-112-62 0-112 51-112 112 0 62 51 112 113 112z" fill="currentColor"/></svg>
|
||||||
<svg v-else xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
<svg v-else xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path
|
<path
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"
|
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 468 548"><path d="M42 122v352c0 35 29 64 64 64h256c35 0 64-29 64-64V122H42zm64 88c0-13 11-24 24-24s24 11 24 24v240c0 13-11 24-24 24s-24-11-24-24V210zm104 0c0-13 11-24 24-24s24 11 24 24v240c0 13-11 24-24 24s-24-11-24-24V210zm104 0c0-13 11-24 24-24s24 11 24 24v240c0 13-11 24-24 24s-24-11-24-24V210z" fill="color-mix(in srgb, var(--colors-danger-DEFAULT) 40%, transparent)"/><path d="M177 10c-14 0-26 9-30 22l-9 26H42c-18 0-32 14-32 32s14 32 32 32h384c18 0 32-14 32-32s-14-32-32-32h-96l-9-26c-4-13-16-22-30-22H177z" fill="var(--colors-danger-DEFAULT)"/></svg>
|
||||||
stroke="none">
|
|
||||||
<path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z" />
|
|
||||||
</svg>
|
|
||||||
<svg v-else xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
<svg v-else xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<polyline points="3 6 5 6 21 6"></polyline>
|
<polyline points="3 6 5 6 21 6"></polyline>
|
||||||
|
|||||||
@@ -2,15 +2,15 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" v-if="filled" class="min-w-[28px]" viewBox="0 0 596 468">
|
<svg xmlns="http://www.w3.org/2000/svg" v-if="filled" class="min-w-[28px]" viewBox="0 0 596 468">
|
||||||
<path
|
<path
|
||||||
d="M10 314c0-63 41-117 98-136-1-8-2-16-2-24 0-79 65-144 144-144 55 0 104 31 128 77 14-8 30-13 48-13 53 0 96 43 96 96 0 16-4 31-10 44 44 20 74 64 74 116 0 71-57 128-128 128H154c-79 0-144-64-144-144zm199-73c-9 9-9 25 0 34s25 9 34 0l31-31v102c0 13 11 24 24 24s24-11 24-24V244l31 31c9 9 25 9 34 0s9-25 0-34l-72-72c-10-9-25-9-34 0l-72 72z"
|
d="M10 314c0-63 41-117 98-136-1-8-2-16-2-24 0-79 65-144 144-144 55 0 104 31 128 77 14-8 30-13 48-13 53 0 96 43 96 96 0 16-4 31-10 44 44 20 74 64 74 116 0 71-57 128-128 128H154c-79 0-144-64-144-144zm199-73c-9 9-9 25 0 34s25 9 34 0l31-31v102c0 13 11 24 24 24s24-11 24-24V244l31 31c9 9 25 9 34 0s9-25 0-34l-72-72c-10-9-25-9-34 0l-72 72z"
|
||||||
fill="#a6acb9" />
|
fill="var(--fill1)" />
|
||||||
<path
|
<path
|
||||||
d="M281 169c9-9 25-9 34 0l72 72c9 9 9 25 0 34s-25 9-34 0l-31-31v102c0 13-11 24-24 24s-24-11-24-24V244l-31 31c-9 9-25 9-34 0s-9-25 0-34l72-72z"
|
d="M281 169c9-9 25-9 34 0l72 72c9 9 9 25 0 34s-25 9-34 0l-31-31v102c0 13-11 24-24 24s-24-11-24-24V244l-31 31c-9 9-25 9-34 0s-9-25 0-34l72-72z"
|
||||||
fill="#1e3050" />
|
fill="currentColor" />
|
||||||
</svg>
|
</svg>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" v-else class="min-w-[28px]" viewBox="-10 -226 596 468">
|
<svg xmlns="http://www.w3.org/2000/svg" v-else class="min-w-[28px]" viewBox="-10 -226 596 468">
|
||||||
<path
|
<path
|
||||||
d="M240-216c-88 0-160 72-160 160 0 5 0 10 1 15C33-18 0 31 0 88c0 80 65 144 144 144h304c71 0 128-57 128-128 0-50-28-93-70-114 4-12 6-25 6-38 0-66-54-120-120-120-11 0-23 2-33 5-30-33-72-53-119-53zM128-56c0-62 50-112 112-112 38 0 71 19 91 47 7 10 20 13 30 8 9-4 20-7 31-7 40 0 72 32 72 72 0 14-4 27-11 38-4 7-5 15-2 22s9 13 16 14c35 9 61 41 61 78 0 44-36 80-80 80H144c-53 0-96-43-96-96 0-43 28-79 67-91 11-4 18-16 16-29-2-7-3-16-3-24zm177 7c-9-9-25-9-34 0l-64 64c-9 9-9 25 0 34 10 9 25 9 34 0l23-23v86c0 13 11 24 24 24s24-11 24-24V26l23 23c9 9 25 9 34 0s9-25 0-34l-64-64z"
|
d="M240-216c-88 0-160 72-160 160 0 5 0 10 1 15C33-18 0 31 0 88c0 80 65 144 144 144h304c71 0 128-57 128-128 0-50-28-93-70-114 4-12 6-25 6-38 0-66-54-120-120-120-11 0-23 2-33 5-30-33-72-53-119-53zM128-56c0-62 50-112 112-112 38 0 71 19 91 47 7 10 20 13 30 8 9-4 20-7 31-7 40 0 72 32 72 72 0 14-4 27-11 38-4 7-5 15-2 22s9 13 16 14c35 9 61 41 61 78 0 44-36 80-80 80H144c-53 0-96-43-96-96 0-43 28-79 67-91 11-4 18-16 16-29-2-7-3-16-3-24zm177 7c-9-9-25-9-34 0l-64 64c-9 9-9 25 0 34 10 9 25 9 34 0l23-23v86c0 13 11 24 24 24s24-11 24-24V26l23 23c9 9 25 9 34 0s9-25 0-34l-64-64z"
|
||||||
fill="#1e3050" />
|
fill="currentColor" />
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|||||||
10
src/components/icons/UserIcon copy.vue
Normal file
10
src/components/icons/UserIcon copy.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
defineProps<{
|
||||||
|
filled?: boolean;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 531"><path d="M10 150c1 99 41 281 214 363 16 8 36 8 52 0 173-82 214-264 214-363 0-26-16-48-38-57L263 13c-4-2-9-3-13-3s-8 1-12 3L48 93c-22 9-38 31-38 57zm128 212c0-44 36-80 80-80h64c44 0 80 36 80 80 0 9-7 16-16 16H154c-9 0-16-7-16-16zm168-176c0 31-25 56-56 56s-56-25-56-56 25-56 56-56 56 25 56 56z" fill="#a6acb9"/><path d="M282 282c44 0 80 36 80 80 0 9-7 16-16 16H154c-9 0-16-7-16-16 0-44 36-80 80-80h64zm-32-40c-31 0-56-25-56-56s25-56 56-56 56 25 56 56-25 56-56 56z" fill="currentColor"/></svg>
|
||||||
|
<svg v-else xmlns="http://www.w3.org/2000/svg" width="518" height="532" viewBox="-3 -258 518 532"><path d="M368 120h-33l-22-64H199l-21 64h-34l32-96h160l32 96zM256-8c-35 0-64-29-64-64s29-64 64-64c36 0 64 29 64 64S292-8 256-8zm0-96c-17 0-32 14-32 32s15 32 32 32c18 0 32-14 32-32s-14-32-32-32zm0 368-12-5C92 193 7 26 17-135l1-20 238-93 239 93 1 20c9 161-76 328-227 394l-13 5zM49-133c-7 147 67 302 207 362 140-60 215-215 208-362l-208-81-207 81z" fill="currentColor"/></svg>
|
||||||
|
</template>
|
||||||
@@ -5,6 +5,6 @@ defineProps<{
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 531"><path d="M10 150c1 99 41 281 214 363 16 8 36 8 52 0 173-82 214-264 214-363 0-26-16-48-38-57L263 13c-4-2-9-3-13-3s-8 1-12 3L48 93c-22 9-38 31-38 57zm128 212c0-44 36-80 80-80h64c44 0 80 36 80 80 0 9-7 16-16 16H154c-9 0-16-7-16-16zm168-176c0 31-25 56-56 56s-56-25-56-56 25-56 56-56 56 25 56 56z" fill="#a6acb9"/><path d="M282 282c44 0 80 36 80 80 0 9-7 16-16 16H154c-9 0-16-7-16-16 0-44 36-80 80-80h64zm-32-40c-31 0-56-25-56-56s25-56 56-56 56 25 56 56-25 56-56 56z" fill="#1e3050"/></svg>
|
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 531"><path d="M10 150c1 99 41 281 214 363 16 8 36 8 52 0 173-82 214-264 214-363 0-26-16-48-38-57L263 13c-4-2-9-3-13-3s-8 1-12 3L48 93c-22 9-38 31-38 57zm128 212c0-44 36-80 80-80h64c44 0 80 36 80 80 0 9-7 16-16 16H154c-9 0-16-7-16-16zm168-176c0 31-25 56-56 56s-56-25-56-56 25-56 56-56 56 25 56 56z" fill="#a6acb9"/><path d="M282 282c44 0 80 36 80 80 0 9-7 16-16 16H154c-9 0-16-7-16-16 0-44 36-80 80-80h64zm-32-40c-31 0-56-25-56-56s25-56 56-56 56 25 56 56-25 56-56 56z" fill="currentColor"/></svg>
|
||||||
<svg v-else xmlns="http://www.w3.org/2000/svg" width="518" height="532" viewBox="-3 -258 518 532"><path d="M368 120h-33l-22-64H199l-21 64h-34l32-96h160l32 96zM256-8c-35 0-64-29-64-64s29-64 64-64c36 0 64 29 64 64S292-8 256-8zm0-96c-17 0-32 14-32 32s15 32 32 32c18 0 32-14 32-32s-14-32-32-32zm0 368-12-5C92 193 7 26 17-135l1-20 238-93 239 93 1 20c9 161-76 328-227 394l-13 5zM49-133c-7 147 67 302 207 362 140-60 215-215 208-362l-208-81-207 81z" fill="#1e3050"/></svg>
|
<svg v-else xmlns="http://www.w3.org/2000/svg" width="518" height="532" viewBox="-3 -258 518 532"><path d="M368 120h-33l-22-64H199l-21 64h-34l32-96h160l32 96zM256-8c-35 0-64-29-64-64s29-64 64-64c36 0 64 29 64 64S292-8 256-8zm0-96c-17 0-32 14-32 32s15 32 32 32c18 0 32-14 32-32s-14-32-32-32zm0 368-12-5C92 193 7 26 17-135l1-20 238-93 239 93 1 20c9 161-76 328-227 394l-13 5zM49-133c-7 147 67 302 207 362 140-60 215-215 208-362l-208-81-207 81z" fill="currentColor"/></svg>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" v-if="filled" viewBox="0 0 532 404">
|
<svg xmlns="http://www.w3.org/2000/svg" v-if="filled" viewBox="0 0 532 404">
|
||||||
<path d="M10 74v256c0 35 29 64 64 64h256c35 0 64-29 64-64V74c0-35-29-64-64-64H74c-35 0-64 29-64 64z"
|
<path d="M10 74v256c0 35 29 64 64 64h256c35 0 64-29 64-64V74c0-35-29-64-64-64H74c-35 0-64 29-64 64z"
|
||||||
fill="#a6acb9" />
|
fill="var(--fill1)" />
|
||||||
<path d="M394 135v134l90 72c4 3 9 5 14 5 13 0 24-11 24-24V82c0-13-11-24-24-24-5 0-10 2-14 5l-90 72z"
|
<path d="M394 135v134l90 72c4 3 9 5 14 5 13 0 24-11 24-24V82c0-13-11-24-24-24-5 0-10 2-14 5l-90 72z"
|
||||||
fill="#1e3050" />
|
fill="currentColor" />
|
||||||
</svg>
|
</svg>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" v-else viewBox="22 -194 564 404">
|
<svg xmlns="http://www.w3.org/2000/svg" v-else viewBox="22 -194 564 404">
|
||||||
<path
|
<path
|
||||||
d="M96-136c-9 0-16 7-16 16v256c0 9 7 16 16 16h256c9 0 16-7 16-16v-256c0-9-7-16-16-16H96zm-64 16c0-35 29-64 64-64h256c35 0 64 29 64 64v256c0 35-29 64-64 64H96c-35 0-64-29-64-64v-256zm506-11c4-3 9-5 14-5 13 0 24 11 24 24v240c0 13-11 24-24 24-5 0-10-2-14-5l-74-55V32l64 48V-64l-64 48v-60l74-55z"
|
d="M96-136c-9 0-16 7-16 16v256c0 9 7 16 16 16h256c9 0 16-7 16-16v-256c0-9-7-16-16-16H96zm-64 16c0-35 29-64 64-64h256c35 0 64 29 64 64v256c0 35-29 64-64 64H96c-35 0-64-29-64-64v-256zm506-11c4-3 9-5 14-5 13 0 24 11 24 24v240c0 13-11 24-24 24-5 0-10-2-14-5l-74-55V32l64 48V-64l-64 48v-60l74-55z"
|
||||||
fill="#1e3050" />
|
fill="currentColor" />
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 564 468"><path d="M42 170h241c-40 35-65 87-65 144 0 17 2 33 6 48H74c-18 0-32-14-32-32V170z" fill="#a6acb9"/><path d="M458 42H345l-96 96h84c-18 8-35 19-50 32H42v160c0 18 14 32 32 32h150c3 11 7 22 11 32H74c-35 0-64-29-64-64V74c0-35 29-64 64-64h384c35 0 64 29 64 64v84c-11-8-23-15-35-20h3V74c0-5-1-10-3-14l-63 63c-5-1-9-1-14-1-11 0-23 1-33 3l82-83h-1zM43 138l96-96H74c-18 0-32 14-32 32v64h1zm46 0h114l96-96H185l-96 96zm321 288c62 0 112-50 112-112s-50-112-112-112-112 50-112 112 50 112 112 112zm0-256c80 0 144 64 144 144s-64 144-144 144-144-64-144-144 64-144 144-144zm-40 74c5-3 11-3 16 0l94 56c4 3 7 8 7 14s-3 11-7 14l-94 56c-5 3-11 3-16 0s-8-8-8-14V258c0-6 3-11 8-14zm24 98 46-28-46-28v56z" fill="#1e3050"/></svg>
|
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 564 468"><path d="M10 74v256c0 35 29 64 64 64h161c-11-24-17-51-17-80 0-106 86-192 192-192 42 0 81 13 112 36V74c0-15-5-29-14-40l-46 46c-16-4-34-6-52-6h-10l64-64h-92l-1 1-95 95h-68l96-96h-92l-1 1-95 95H48l96-96H74c-35 0-64 29-64 64z" fill="var(--fill1)"/><path d="M266 314c0-80 64-144 144-144s144 64 144 144-64 144-144 144-144-64-144-144zm104-62c-5 3-8 8-8 14v96c0 6 3 11 8 14s11 3 16 0l80-48c5-3 8-8 8-14s-3-11-8-14l-80-48c-5-3-11-3-16 0z" fill="currentColor"/></svg>
|
||||||
<svg v-else xmlns="http://www.w3.org/2000/svg" width="564" height="468" viewBox="22 -194 564 468"><path d="M480-152H367l-96 96h84c-18 8-35 19-50 32H64v160c0 18 14 32 32 32h150c3 11 7 22 11 32H96c-35 0-64-29-64-64v-256c0-35 29-64 64-64h384c35 0 64 29 64 64v84c-11-8-23-15-35-20h3v-64c0-5-1-10-3-14l-63 63c-5-1-9-1-14-1-11 0-23 1-33 3l82-83h-1zM65-56l96-96H96c-18 0-32 14-32 32v64h1zm46 0h114l96-96H207l-96 96zm321 288c62 0 112-50 112-112S494 8 432 8 320 58 320 120s50 112 112 112zm0-256c80 0 144 64 144 144s-64 144-144 144-144-64-144-144S352-24 432-24zm-40 74c5-3 11-3 16 0l94 56c4 3 7 8 7 14s-3 11-7 14l-94 56c-5 3-11 3-16 0s-8-8-8-14V64c0-6 3-11 8-14zm24 98 46-28-46-28v56z" fill="#1e3050"/></svg>
|
<svg v-else xmlns="http://www.w3.org/2000/svg" width="564" height="468" viewBox="22 -194 564 468"><path d="M480-152H367l-96 96h84c-18 8-35 19-50 32H64v160c0 18 14 32 32 32h150c3 11 7 22 11 32H96c-35 0-64-29-64-64v-256c0-35 29-64 64-64h384c35 0 64 29 64 64v84c-11-8-23-15-35-20h3v-64c0-5-1-10-3-14l-63 63c-5-1-9-1-14-1-11 0-23 1-33 3l82-83h-1zM65-56l96-96H96c-18 0-32 14-32 32v64h1zm46 0h114l96-96H207l-96 96zm321 288c62 0 112-50 112-112S494 8 432 8 320 58 320 120s50 112 112 112zm0-256c80 0 144 64 144 144s-64 144-144 144-144-64-144-144S352-24 432-24zm-40 74c5-3 11-3 16 0l94 56c4 3 7 8 7 14s-3 11-7 14l-94 56c-5 3-11 3-16 0s-8-8-8-14V64c0-6 3-11 8-14zm24 98 46-28-46-28v56z" fill="currentColor"/></svg>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
|||||||
8
src/components/icons/hard-drive.vue
Normal file
8
src/components/icons/hard-drive.vue
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<template>
|
||||||
|
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 468 468"><path d="M10 74c0-35 29-64 64-64h320c35 0 64 29 64 64v256c0-35-29-64-64-64H74c-35 0-64 29-64 64V74zm288 288c0 18-14 32-32 32s-32-14-32-32 14-32 32-32 32 14 32 32z" fill="var(--fill1)"/><path d="M10 330c0-35 29-64 64-64h320c35 0 64 29 64 64v64c0 35-29 64-64 64H74c-35 0-64-29-64-64v-64zm288 32c0-18-14-32-32-32s-32 14-32 32 14 32 32 32 32-14 32-32zm64 32c18 0 32-14 32-32s-14-32-32-32-32 14-32 32 14 32 32 32z" fill="currentColor"/></svg>
|
||||||
|
<svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="-10 -226 468 468"><path d="M64-184c-18 0-32 14-32 32V17c9-6 20-9 32-9h320c12 0 23 3 32 9v-169c0-18-14-32-32-32H64zM32 72v96c0 18 14 32 32 32h320c18 0 32-14 32-32V72c0-18-14-32-32-32H64c-18 0-32 14-32 32zM0 72v-224c0-35 29-64 64-64h320c35 0 64 29 64 64v320c0 35-29 64-64 64H64c-35 0-64-29-64-64V72zm256 24c13 0 24 11 24 24s-11 24-24 24-24-11-24-24 11-24 24-24zm96 0c13 0 24 11 24 24s-11 24-24 24-24-11-24-24 11-24 24-24z" fill="currentColor"/></svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
defineProps<{ filled?: boolean }>();
|
||||||
|
</script>
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import { createStaticVNode } from "vue";
|
|
||||||
|
|
||||||
export const Home = createStaticVNode(`<svg aria-hidden="true" aria-label="" class="v-mid m-a" height="24" role="img" viewBox="0 0 24 24" width="24">
|
|
||||||
<path
|
|
||||||
d="M4.6 22.73A107 107 0 0 0 11 23h2.22c2.43-.04 4.6-.16 6.18-.27A3.9 3.9 0 0 0 23 18.8v-8.46a4 4 0 0 0-1.34-3L14.4.93a3.63 3.63 0 0 0-4.82 0L2.34 7.36A4 4 0 0 0 1 10.35v8.46a3.9 3.9 0 0 0 3.6 3.92M13.08 2.4l7.25 6.44a2 2 0 0 1 .67 1.5v8.46a1.9 1.9 0 0 1-1.74 1.92q-1.39.11-3.26.19V16a4 4 0 0 0-8 0v4.92q-1.87-.08-3.26-.19A1.9 1.9 0 0 1 3 18.81v-8.46a2 2 0 0 1 .67-1.5l7.25-6.44a1.63 1.63 0 0 1 2.16 0M13.12 21h-2.24a1 1 0 0 1-.88-1v-4a2 2 0 1 1 4 0v4a1 1 0 0 1-.88 1">
|
|
||||||
</path>
|
|
||||||
</svg>`, 1);
|
|
||||||
export const HomeFilled = createStaticVNode(`<svg aria-hidden="true" aria-label="" class="v-mid m-a" height="24" role="img" viewBox="0 0 24 24" width="24">
|
|
||||||
<path
|
|
||||||
d="M9.59.92a3.63 3.63 0 0 1 4.82 0l7.25 6.44A4 4 0 0 1 23 10.35v8.46a3.9 3.9 0 0 1-3.6 3.92 106 106 0 0 1-14.8 0A3.9 3.9 0 0 1 1 18.8v-8.46a4 4 0 0 1 1.34-3zM12 16a5 5 0 0 1-3.05-1.04l-1.23 1.58a7 7 0 0 0 8.56 0l-1.23-1.58A5 5 0 0 1 12 16">
|
|
||||||
</path>
|
|
||||||
</svg>`, 1);
|
|
||||||
export const Dashboard = createStaticVNode(`<svg aria-hidden="true" aria-label="" class="v-mid m-a" height="24" role="img" viewBox="0 0 24 24" width="24">
|
|
||||||
<path
|
|
||||||
d="M23 5a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v14a4 4 0 0 0 4 4h14a4 4 0 0 0 4-4zm-10 6V3h6a2 2 0 0 1 2 2v6zm8 8a2 2 0 0 1-2 2h-6v-8h8zM5 3h6v18H5a2 2 0 0 1-2-2V5c0-1.1.9-2 2-2">
|
|
||||||
</path>
|
|
||||||
</svg>`, 1);
|
|
||||||
export const DashboardFilled = createStaticVNode(`<svg aria-hidden="true" aria-label="" class="v-mid m-a" height="24" role="img" viewBox="0 0 24 24" width="24">
|
|
||||||
<path
|
|
||||||
d="M11 23H5a4 4 0 0 1-4-4V5a4 4 0 0 1 4-4h6zm12-4a4 4 0 0 1-4 4h-6V13h10zM19 1a4 4 0 0 1 4 4v6H13V1z">
|
|
||||||
</path>
|
|
||||||
</svg>`, 1);
|
|
||||||
export const Add = createStaticVNode(`<svg aria-hidden="true" aria-label="" class="v-mid m-a" height="24" role="img" viewBox="0 0 24 24" width="24">
|
|
||||||
<path
|
|
||||||
d="M11 11H6v2h5v5h2v-5h5v-2h-5V6h-2zM5 1a4 4 0 0 0-4 4v14a4 4 0 0 0 4 4h14a4 4 0 0 0 4-4V5a4 4 0 0 0-4-4zm16 4v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5c0-1.1.9-2 2-2h14a2 2 0 0 1 2 2">
|
|
||||||
</path>
|
|
||||||
</svg>`, 1);
|
|
||||||
export const AddFilled = createStaticVNode(`<svg aria-hidden="true" aria-label="" class="v-mid m-a" height="24" role="img" viewBox="0 0 24 24" width="24">
|
|
||||||
<path
|
|
||||||
d="M1 5a4 4 0 0 1 4-4h14a4 4 0 0 1 4 4v14a4 4 0 0 1-4 4H5a4 4 0 0 1-4-4zm10 6H6v2h5v5h2v-5h5v-2h-5V6h-2z">
|
|
||||||
</path>
|
|
||||||
</svg>`, 1);
|
|
||||||
export const Bell = createStaticVNode(`<svg aria-hidden="true" aria-label="" class="v-mid m-a" height="24" role="img" viewBox="0 0 24 24" width="24">
|
|
||||||
<path
|
|
||||||
d="M16 19h8v-2h-.34a3.15 3.15 0 0 1-3.12-2.76l-.8-6.41a7.8 7.8 0 0 0-15.48 0l-.8 6.41A3.15 3.15 0 0 1 .34 17H0v2h8v1h.02a3.4 3.4 0 0 0 3.38 3h1.2a3.4 3.4 0 0 0 3.38-3H16zm1.75-10.92.8 6.4c.12.95.5 1.81 1.04 2.52H4.4c.55-.7.92-1.57 1.04-2.51l.8-6.41a5.8 5.8 0 0 1 11.5 0M13.4 19c.33 0 .6.27.6.6 0 .77-.63 1.4-1.4 1.4h-1.2a1.4 1.4 0 0 1-1.4-1.4c0-.33.27-.6.6-.6z">
|
|
||||||
</path>
|
|
||||||
</svg>`, 1);
|
|
||||||
export const BellFilled = createStaticVNode(`<svg aria-hidden="true" aria-label="" class="v-mid m-a" height="24" role="img" viewBox="0 0 24 24" width="24">
|
|
||||||
<path
|
|
||||||
d="M20.54 14.24A3.15 3.15 0 0 0 23.66 17H24v2h-8v1h-.02a3.4 3.4 0 0 1-3.38 3h-1.2a3.4 3.4 0 0 1-3.38-3H8v-1H0v-2h.34a3.15 3.15 0 0 0 3.12-2.76l.8-6.41a7.8 7.8 0 0 1 15.48 0zM10 19.6c0 .77.63 1.4 1.4 1.4h1.2c.77 0 1.4-.63 1.4-1.4a.6.6 0 0 0-.6-.6h-2.8a.6.6 0 0 0-.6.6" ></path>
|
|
||||||
</svg>`, 1);
|
|
||||||
export const Search = createStaticVNode(`<svg aria-hidden="true" aria-label="" class="v-mid m-a" height="24" role="img" viewBox="0 0 24 24" width="24"><path d="M17.33 18.74a10 10 0 1 1 1.41-1.41l4.47 4.47-1.41 1.41zM11 3a8 8 0 1 0 0 16 8 8 0 0 0 0-16"></path></svg>`, 1);
|
|
||||||
7
src/components/icons/shield-user.vue
Normal file
7
src/components/icons/shield-user.vue
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<template>
|
||||||
|
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 531"><path d="M10 150c1 99 41 281 214 363 16 8 36 8 52 0 173-82 214-264 214-363 0-26-16-48-38-57L263 13c-4-2-9-3-13-3s-8 1-12 3L48 93c-22 9-38 31-38 57zm128 212c0-44 36-80 80-80h64c44 0 80 36 80 80 0 9-7 16-16 16H154c-9 0-16-7-16-16zm168-176c0 31-25 56-56 56s-56-25-56-56 25-56 56-56 56 25 56 56z" fill="var(--fill1)"/><path d="M282 282c44 0 80 36 80 80 0 9-7 16-16 16H154c-9 0-16-7-16-16 0-44 36-80 80-80h64zm-32-40c-31 0-56-25-56-56s25-56 56-56 56 25 56 56-25 56-56 56z" fill="currentColor"/></svg>
|
||||||
|
<svg v-else xmlns="http://www.w3.org/2000/svg" width="500" height="529" viewBox="6 -257 500 529"><path d="M231-240c16-7 34-7 50 0l177 75 4 2c18 9 32 28 34 50v24c-6 102-52 267-214 344l-5 2c-11 5-24 5-35 2l-6-1-6-3C68 178 22 14 17-88l-1-20c0-25 14-45 34-55l4-2 177-75zm38 29c-7-3-15-3-22-1l-3 1-177 75c-11 5-19 16-19 28l1 18c5 96 48 246 194 316l4 2c7 2 15 2 22-2l14-7c144-78 181-236 181-327v-3c-1-10-7-19-17-24l-2-1-176-75zm19 235c44 0 80 36 80 80 0 9-7 16-16 16s-16-7-16-16c0-26-21-48-48-48h-64c-26 0-48 22-48 48 0 9-7 16-16 16s-16-7-16-16c0-44 36-80 80-80h64zM256-8c-35 0-64-29-64-64s29-64 64-64 64 29 64 64-29 64-64 64zm0-96c-18 0-32 14-32 32s14 32 32 32 32-14 32-32-14-32-32-32z" fill="currentColor"/></svg>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
defineProps<{ filled?: boolean }>();
|
||||||
|
</script>
|
||||||
7
src/components/icons/windows.vue
Normal file
7
src/components/icons/windows.vue
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<template>
|
||||||
|
<svg v-if="filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 596 468"><path d="M170 74v64h64V74h288v192h-96v64h96c35 0 64-29 64-64V74c0-35-29-64-64-64H234c-35 0-64 29-64 64z" fill="var(--fill1)"/><path d="M74 138c-35 0-64 29-64 64v192c0 35 29 64 64 64h288c35 0 64-29 64-64V202c0-35-29-64-64-64H74zm24 80h240c13 0 24 11 24 24s-11 24-24 24H98c-13 0-24-11-24-24s11-24 24-24z" fill="var(--fill4)"/></svg>
|
||||||
|
<svg v-else xmlns="http://www.w3.org/2000/svg" width="596" height="468" viewBox="-10 -226 596 468"><path d="M512-184H224c-18 0-32 14-32 32v16h-32v-16c0-35 29-64 64-64h288c35 0 64 29 64 64V40c0 35-29 64-64 64h-48V72h48c18 0 32-14 32-32v-192c0-18-14-32-32-32zM352-56H64c-18 0-32 14-32 32V8h352v-32c0-18-14-32-32-32zm32 96H32v128c0 18 14 32 32 32h288c18 0 32-14 32-32V40zM64-88h288c35 0 64 29 64 64v192c0 35-29 64-64 64H64c-35 0-64-29-64-64V-24c0-35 29-64 64-64z" fill="currentColor"/></svg>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
defineProps<{ filled?: boolean }>();
|
||||||
|
</script>
|
||||||
71
src/components/ui/AppButton.vue
Normal file
71
src/components/ui/AppButton.vue
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import { cva } from "class-variance-authority";
|
||||||
|
import { ButtonHTMLAttributes, computed } from 'vue';
|
||||||
|
type UiButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger';
|
||||||
|
type UiButtonSize = 'sm' | 'md' | 'lg' | 'icon' | 'icon-sm' | 'icon-lg';
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
variant?: UiButtonVariant;
|
||||||
|
size?: UiButtonSize;
|
||||||
|
block?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
loading?: boolean;
|
||||||
|
type?: 'button' | 'submit' | 'reset';
|
||||||
|
onClick?: ButtonHTMLAttributes['onClick'];
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
variant: 'primary',
|
||||||
|
size: 'md',
|
||||||
|
block: false,
|
||||||
|
disabled: false,
|
||||||
|
loading: false,
|
||||||
|
type: 'button',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const isDisabled = computed(() => props.disabled || props.loading);
|
||||||
|
const buttonVariants = cva(":uno: inline-flex items-center justify-center gap-2 rounded-md border font-medium whitespace-nowrap shadow-[0_1px_0_rgba(27,31,36,0.04),0_1px_3px_rgba(27,31,36,0.12)] outline-none transition-[transform,box-shadow,background-color,border-color,color,opacity] duration-150 ease-out active:translate-y-[0.5px] hover:shadow-[0_2px_0_rgba(27,31,36,0.06)] disabled:cursor-not-allowed disabled:opacity-60 focus-visible:ring-4",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
primary: 'border-transparent bg-primary text-white hover:bg-primaryHover focus-visible:ring-primary/25',
|
||||||
|
secondary: 'border-border bg-white text-text hover:bg-header focus-visible:ring-#0969da/20',
|
||||||
|
ghost: 'border-transparent bg-transparent text-text hover:bg-header focus-visible:ring-#0969da/20 shadow-none',
|
||||||
|
danger: 'border-transparent bg-danger text-white hover:opacity-92 focus-visible:ring-danger/20',
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
sm: 'min-h-[28px] px-3 text-[12px] leading-[20px]',
|
||||||
|
md: 'min-h-[32px] px-3 text-[14px] leading-[20px]',
|
||||||
|
lg: 'min-h-[36px] px-4 text-[14px] leading-[20px]',
|
||||||
|
icon: 'min-h-0 p-2',
|
||||||
|
'icon-sm': 'min-h-0 p-1',
|
||||||
|
'icon-lg': 'min-h-0 p-3',
|
||||||
|
},
|
||||||
|
block: {
|
||||||
|
true: 'w-full',
|
||||||
|
false: '',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: props.variant,
|
||||||
|
size: props.size,
|
||||||
|
block: props.block,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<button :type="type" :disabled="isDisabled" :class="cn(buttonVariants({variant, size, block}))" v-on:click="onClick" :aria-busy="loading || undefined">
|
||||||
|
<span
|
||||||
|
v-if="loading"
|
||||||
|
class="h-4 w-4 shrink-0 animate-spin rounded-full border-2 border-current border-r-transparent"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
<slot v-else name="icon" />
|
||||||
|
<slot />
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import AppButton from '@/components/app/AppButton.vue';
|
import AppButton from '@/components/ui/AppButton.vue';
|
||||||
import AppDialog from '@/components/app/AppDialog.vue';
|
import AppDialog from '@/components/ui/AppDialog.vue';
|
||||||
import AlertTriangleIcon from '@/components/icons/AlertTriangleIcon.vue';
|
import AlertTriangleIcon from '@/components/icons/AlertTriangleIcon.vue';
|
||||||
import { useAppConfirm } from '@/composables/useAppConfirm';
|
import { useAppConfirm } from '@/composables/useAppConfirm';
|
||||||
|
|
||||||
117
src/components/ui/AppDialog.vue
Normal file
117
src/components/ui/AppDialog.vue
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import XIcon from '@/components/icons/XIcon.vue';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
import { onBeforeUnmount, watch } from 'vue';
|
||||||
|
import ClientOnly from '../ClientOnly';
|
||||||
|
|
||||||
|
// Ensure client-side only rendering to avoid hydration mismatch
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
visible: boolean;
|
||||||
|
title?: string;
|
||||||
|
closable?: boolean;
|
||||||
|
maxWidthClass?: string;
|
||||||
|
}>(), {
|
||||||
|
title: '',
|
||||||
|
closable: true,
|
||||||
|
maxWidthClass: 'max-w-lg',
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:visible', value: boolean): void;
|
||||||
|
(e: 'close'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
emit('update:visible', false);
|
||||||
|
emit('close');
|
||||||
|
};
|
||||||
|
|
||||||
|
const onKeydown = (e: KeyboardEvent) => {
|
||||||
|
if (!props.visible) return;
|
||||||
|
if (!props.closable) return;
|
||||||
|
if (e.key === 'Escape') close();
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.visible,
|
||||||
|
(v) => {
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
if (v) {
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
window.addEventListener('keydown', onKeydown);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
document.body.style.overflow = 'unset';
|
||||||
|
window.removeEventListener('keydown', onKeydown);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
window.removeEventListener('keydown', onKeydown);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ClientOnly>
|
||||||
|
<Teleport to="body">
|
||||||
|
<Transition
|
||||||
|
enter-active-class="transition-all duration-200 ease-out"
|
||||||
|
enter-from-class="opacity-0"
|
||||||
|
enter-to-class="opacity-100"
|
||||||
|
leave-active-class="transition-all duration-150 ease-in"
|
||||||
|
leave-from-class="opacity-100"
|
||||||
|
leave-to-class="opacity-0"
|
||||||
|
>
|
||||||
|
<div v-if="visible" class="fixed inset-0 z-[9999]">
|
||||||
|
<!-- Backdrop -->
|
||||||
|
<div
|
||||||
|
class="absolute inset-0 bg-black/40"
|
||||||
|
@click="closable && close()"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Panel -->
|
||||||
|
<div class="absolute inset-0 flex items-center justify-center p-4">
|
||||||
|
<div :class="cn('w-full bg-white border border-border rounded-lg shadow-lg overflow-hidden', maxWidthClass)">
|
||||||
|
<!-- Header slot -->
|
||||||
|
<div v-if="$slots.header" class="px-5 py-4 border-b border-border">
|
||||||
|
<slot name="header" :close="close" />
|
||||||
|
</div>
|
||||||
|
<!-- Default title -->
|
||||||
|
<div v-else-if="title" class="flex items-center justify-between gap-3 px-5 py-4 border-b border-border">
|
||||||
|
<h3 class="font-semibold text-foreground">
|
||||||
|
{{ title }}
|
||||||
|
</h3>
|
||||||
|
<button
|
||||||
|
v-if="closable"
|
||||||
|
type="button"
|
||||||
|
class="p-1 rounded-md text-foreground/60 hover:text-foreground hover:bg-muted/50 transition-all"
|
||||||
|
@click="close"
|
||||||
|
:aria-label="t('common.close')"
|
||||||
|
>
|
||||||
|
<XIcon class="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Content -->
|
||||||
|
<div class="p-5 max-h-[80vh] overflow-y-auto">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer slot -->
|
||||||
|
<div v-if="$slots.footer" class="px-5 py-4 border-t border-border bg-muted/20">
|
||||||
|
<slot name="footer" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</Teleport>
|
||||||
|
</ClientOnly>
|
||||||
|
</template>
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { computed } from 'vue';
|
import { computed, useSlots } from 'vue';
|
||||||
|
|
||||||
// Vue macro is available at compile time; provide a safe fallback for typecheck.
|
// Vue macro is available at compile time; provide a safe fallback for typecheck.
|
||||||
declare const defineModelModifiers: undefined | (<T>() => T);
|
declare const defineModelModifiers: undefined | (<T>() => T);
|
||||||
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
as?: 'input' | 'textarea' | 'select';
|
||||||
modelValue?: string | number | null;
|
modelValue?: string | number | null;
|
||||||
type?: string;
|
type?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
@@ -21,14 +20,17 @@ type Props = {
|
|||||||
max?: number | string;
|
max?: number | string;
|
||||||
step?: number | string;
|
step?: number | string;
|
||||||
maxlength?: number;
|
maxlength?: number;
|
||||||
|
rows?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
as: 'input',
|
||||||
modelValue: '',
|
modelValue: '',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
placeholder: '',
|
placeholder: '',
|
||||||
readonly: false,
|
readonly: false,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
|
rows: 3,
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -40,10 +42,13 @@ const modelModifiers = (typeof defineModelModifiers === 'function'
|
|||||||
? defineModelModifiers<{ number?: boolean }>()
|
? defineModelModifiers<{ number?: boolean }>()
|
||||||
: ({} as { number?: boolean }));
|
: ({} as { number?: boolean }));
|
||||||
|
|
||||||
const isNumberLike = computed(() => props.type === 'number' || !!modelModifiers.number);
|
const isNumberLike = computed(() => props.as === 'input' && (props.type === 'number' || !!modelModifiers.number));
|
||||||
|
const hasLeadingSlot = computed(() => props.as === 'input' && !!useSlots().prefix);
|
||||||
|
const isTextarea = computed(() => props.as === 'textarea');
|
||||||
|
const isSelect = computed(() => props.as === 'select');
|
||||||
|
|
||||||
const onInput = (e: Event) => {
|
const onInput = (e: Event) => {
|
||||||
const el = e.target as HTMLInputElement;
|
const el = e.target as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
|
||||||
const raw = el.value;
|
const raw = el.value;
|
||||||
if (isNumberLike.value) {
|
if (isNumberLike.value) {
|
||||||
if (raw === '') {
|
if (raw === '') {
|
||||||
@@ -61,16 +66,45 @@ const onKeyup = (e: KeyboardEvent) => {
|
|||||||
if (e.key === 'Enter') emit('enter');
|
if (e.key === 'Enter') emit('enter');
|
||||||
};
|
};
|
||||||
|
|
||||||
const baseInputClass = 'w-full px-3 py-2 rounded-md border border-border bg-surface text-foreground placeholder:text-foreground/40 focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary/50 disabled:opacity-60 disabled:cursor-not-allowed';
|
const baseInputClass = 'w-full px-3 py-2 rounded-md border border-border bg-header text-foreground placeholder:text-foreground/40 focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary/50 disabled:opacity-60 disabled:cursor-not-allowed';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="cn('relative', wrapperClass)">
|
<div :class="cn('relative', wrapperClass)">
|
||||||
<div v-if="$slots.prefix" class="absolute left-3 top-1/2 -translate-y-1/2 text-foreground/50">
|
<div v-if="hasLeadingSlot" class="absolute left-3 top-1/2 -translate-y-1/2 text-foreground/50">
|
||||||
<slot name="prefix" />
|
<slot name="prefix" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<textarea
|
||||||
|
v-if="isTextarea"
|
||||||
|
:id="id"
|
||||||
|
:name="name"
|
||||||
|
:value="modelValue ?? ''"
|
||||||
|
:rows="rows"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:readonly="readonly"
|
||||||
|
:disabled="disabled"
|
||||||
|
:maxlength="maxlength"
|
||||||
|
:class="cn(baseInputClass, inputClass)"
|
||||||
|
@input="onInput"
|
||||||
|
@keyup="onKeyup"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<select
|
||||||
|
v-else-if="isSelect"
|
||||||
|
:id="id"
|
||||||
|
:name="name"
|
||||||
|
:value="modelValue ?? ''"
|
||||||
|
:disabled="disabled"
|
||||||
|
:class="cn(baseInputClass, inputClass)"
|
||||||
|
@change="onInput"
|
||||||
|
@keyup="onKeyup"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</select>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
|
v-else
|
||||||
:id="id"
|
:id="id"
|
||||||
:name="name"
|
:name="name"
|
||||||
:type="type"
|
:type="type"
|
||||||
@@ -83,7 +117,7 @@ const baseInputClass = 'w-full px-3 py-2 rounded-md border border-border bg-surf
|
|||||||
:max="max"
|
:max="max"
|
||||||
:step="step"
|
:step="step"
|
||||||
:maxlength="maxlength"
|
:maxlength="maxlength"
|
||||||
:class="cn(baseInputClass, $slots.prefix ? 'pl-10' : '', inputClass)"
|
:class="cn(baseInputClass, hasLeadingSlot ? 'pl-10' : '', inputClass)"
|
||||||
@input="onInput"
|
@input="onInput"
|
||||||
@keyup="onKeyup"
|
@keyup="onKeyup"
|
||||||
/>
|
/>
|
||||||
55
src/components/ui/AppSwitch.vue
Normal file
55
src/components/ui/AppSwitch.vue
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
interface SwitchProps {
|
||||||
|
disabled?: boolean;
|
||||||
|
ariaLabel?: string;
|
||||||
|
class?: string; // Đổi từ className sang class cho chuẩn Vue
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<SwitchProps>(), {
|
||||||
|
disabled: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Vue 3.4+ - Quản lý v-model cực gọn
|
||||||
|
const modelValue = defineModel<boolean>({ default: false });
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'change', value: boolean): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const toggle = () => {
|
||||||
|
if (props.disabled) return;
|
||||||
|
modelValue.value = !modelValue.value;
|
||||||
|
emit('change', modelValue.value);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
role="switch"
|
||||||
|
:aria-checked="modelValue"
|
||||||
|
:aria-label="ariaLabel"
|
||||||
|
:disabled="disabled"
|
||||||
|
@click="toggle"
|
||||||
|
:class="cn(
|
||||||
|
// Layout & Size
|
||||||
|
'relative inline-flex h-6 w-11 shrink-0 items-center rounded-full border-2 border-transparent transition-colors duration-200',
|
||||||
|
// Focus states (UnoCSS style)
|
||||||
|
'outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2',
|
||||||
|
// Status states
|
||||||
|
disabled ? 'op-50 cursor-not-allowed' : 'cursor-pointer',
|
||||||
|
modelValue ? 'bg-primary' : 'bg-gray-200 dark:bg-dark-300',
|
||||||
|
props.class
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
:class="cn(
|
||||||
|
// Toggle thumb
|
||||||
|
'pointer-events-none block h-5 w-5 rounded-full bg-white shadow-sm transition-transform duration-200',
|
||||||
|
modelValue ? 'translate-x-5' : 'translate-x-0'
|
||||||
|
)"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
@@ -91,7 +91,7 @@ onBeforeUnmount(() => {
|
|||||||
type="button"
|
type="button"
|
||||||
class="p-1 rounded-md text-foreground/50 hover:text-foreground hover:bg-muted/50 transition-all"
|
class="p-1 rounded-md text-foreground/50 hover:text-foreground hover:bg-muted/50 transition-all"
|
||||||
@click="dismiss(t.id)"
|
@click="dismiss(t.id)"
|
||||||
aria-label="Dismiss"
|
:aria-label="$t('toast.dismissAria')"
|
||||||
>
|
>
|
||||||
<XIcon class="w-4 h-4" />
|
<XIcon class="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
75
src/components/ui/AsyncSelect.vue
Normal file
75
src/components/ui/AsyncSelect.vue
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
interface SelectOption {
|
||||||
|
label: string;
|
||||||
|
value: string | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
loadOptions: () => Promise<SelectOption[]>;
|
||||||
|
placeholder?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
placeholder: 'Please select...',
|
||||||
|
disabled: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const modelValue = defineModel<string | number>();
|
||||||
|
|
||||||
|
const options = ref<SelectOption[]>([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const error = ref<string | null>(null);
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
error.value = null;
|
||||||
|
try {
|
||||||
|
options.value = await props.loadOptions();
|
||||||
|
} catch {
|
||||||
|
error.value = 'Failed to load options';
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(fetchData);
|
||||||
|
watch(() => props.loadOptions, fetchData);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div class="relative w-full">
|
||||||
|
<select
|
||||||
|
v-model="modelValue"
|
||||||
|
:disabled="loading || disabled"
|
||||||
|
class="w-full appearance-none rounded-md border border-border bg-header px-3 py-2 pr-10 text-sm text-foreground outline-none transition-all focus:border-primary/50 focus:ring-2 focus:ring-primary/30 disabled:cursor-not-allowed disabled:opacity-60"
|
||||||
|
>
|
||||||
|
<option value="" disabled>{{ placeholder }}</option>
|
||||||
|
<option
|
||||||
|
v-for="opt in options"
|
||||||
|
:key="opt.value"
|
||||||
|
:value="opt.value"
|
||||||
|
>
|
||||||
|
{{ opt.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3 text-foreground/40">
|
||||||
|
<div v-if="loading" class="h-4 w-4 animate-spin rounded-full border-2 border-current border-r-transparent" />
|
||||||
|
<div v-else class="i-carbon-chevron-down text-lg" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
v-if="error"
|
||||||
|
type="button"
|
||||||
|
@click="fetchData"
|
||||||
|
class="text-xs font-medium text-danger transition hover:opacity-80"
|
||||||
|
>
|
||||||
|
{{ error }} · Retry
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
254
src/components/ui/BaseTable.vue
Normal file
254
src/components/ui/BaseTable.vue
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
<script setup lang="ts" generic="TData extends Record<string, any>">
|
||||||
|
import AppButton from '@/components/ui/AppButton.vue';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import {
|
||||||
|
FlexRender,
|
||||||
|
getCoreRowModel,
|
||||||
|
getSortedRowModel,
|
||||||
|
useVueTable,
|
||||||
|
type ColumnDef,
|
||||||
|
type ColumnMeta,
|
||||||
|
type Row,
|
||||||
|
type SortingState,
|
||||||
|
type Updater,
|
||||||
|
} from '@tanstack/vue-table';
|
||||||
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
type TableColumnMeta = ColumnMeta<TData, any> & {
|
||||||
|
headerClass?: string;
|
||||||
|
cellClass?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
data: TData[];
|
||||||
|
columns: ColumnDef<TData, any>[];
|
||||||
|
loading?: boolean;
|
||||||
|
emptyText?: string;
|
||||||
|
tableClass?: string;
|
||||||
|
wrapperClass?: string;
|
||||||
|
headerRowClass?: string;
|
||||||
|
bodyRowClass?: string | ((row: Row<TData>) => string | undefined);
|
||||||
|
getRowId?: (originalRow: TData, index: number) => string;
|
||||||
|
pagination?: boolean;
|
||||||
|
currentPage?: number;
|
||||||
|
totalPages?: number;
|
||||||
|
totalRecords?: number;
|
||||||
|
rowsPerPage?: number;
|
||||||
|
pageSizeOptions?: number[];
|
||||||
|
canPreviousPage?: boolean;
|
||||||
|
canNextPage?: boolean;
|
||||||
|
skeletonRows?: number;
|
||||||
|
}>(), {
|
||||||
|
loading: false,
|
||||||
|
emptyText: 'No data available.',
|
||||||
|
pagination: false,
|
||||||
|
currentPage: 1,
|
||||||
|
totalPages: 1,
|
||||||
|
totalRecords: 0,
|
||||||
|
rowsPerPage: 10,
|
||||||
|
pageSizeOptions: () => [],
|
||||||
|
canPreviousPage: false,
|
||||||
|
canNextPage: false,
|
||||||
|
skeletonRows: 10,
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'previous-page'): void;
|
||||||
|
(e: 'next-page'): void;
|
||||||
|
(e: 'page-size-change', value: number): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const sorting = ref<SortingState>([]);
|
||||||
|
|
||||||
|
function updateSorting(updaterOrValue: Updater<SortingState>) {
|
||||||
|
sorting.value = typeof updaterOrValue === 'function'
|
||||||
|
? updaterOrValue(sorting.value)
|
||||||
|
: updaterOrValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const table = useVueTable<TData>({
|
||||||
|
get data() {
|
||||||
|
return props.data;
|
||||||
|
},
|
||||||
|
get columns() {
|
||||||
|
return props.columns;
|
||||||
|
},
|
||||||
|
getRowId: props.getRowId,
|
||||||
|
state: {
|
||||||
|
get sorting() {
|
||||||
|
return sorting.value;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
onSortingChange: updateSorting,
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
getSortedRowModel: getSortedRowModel(),
|
||||||
|
});
|
||||||
|
|
||||||
|
function resolveBodyRowClass(row: Row<TData>) {
|
||||||
|
return typeof props.bodyRowClass === 'function'
|
||||||
|
? props.bodyRowClass(row)
|
||||||
|
: props.bodyRowClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
const shouldRenderPagination = computed(() => (
|
||||||
|
props.pagination
|
||||||
|
&& !props.loading
|
||||||
|
&& table.getRowModel().rows.length > 0
|
||||||
|
));
|
||||||
|
|
||||||
|
const skeletonRowIndexes = computed(() =>
|
||||||
|
Array.from({ length: Math.max(1, props.skeletonRows) }, (_, index) => index)
|
||||||
|
);
|
||||||
|
|
||||||
|
const skeletonColumnIndexes = computed(() =>
|
||||||
|
Array.from({ length: Math.max(1, props.columns.length) }, (_, index) => index)
|
||||||
|
);
|
||||||
|
|
||||||
|
function previousPage() {
|
||||||
|
if (!props.canPreviousPage) return;
|
||||||
|
emit('previous-page');
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextPage() {
|
||||||
|
if (!props.canNextPage) return;
|
||||||
|
emit('next-page');
|
||||||
|
}
|
||||||
|
|
||||||
|
function changePageSize(event: Event) {
|
||||||
|
const nextValue = Number((event.target as HTMLSelectElement).value) || props.rowsPerPage;
|
||||||
|
emit('page-size-change', nextValue);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="cn('overflow-x-auto rounded-xl border border-gray-200 bg-white', wrapperClass)">
|
||||||
|
<table :class="cn('w-full min-w-[48rem] border-collapse', tableClass)">
|
||||||
|
<thead class="bg-header">
|
||||||
|
<tr
|
||||||
|
v-for="headerGroup in table.getHeaderGroups()"
|
||||||
|
:key="headerGroup.id"
|
||||||
|
:class="cn('border-b border-gray-200', headerRowClass)"
|
||||||
|
>
|
||||||
|
<th
|
||||||
|
v-for="header in headerGroup.headers"
|
||||||
|
:key="header.id"
|
||||||
|
:class="cn(
|
||||||
|
'px-4 py-3 text-left text-sm font-medium text-gray-600',
|
||||||
|
header.column.getCanSort() && !header.isPlaceholder && 'cursor-pointer select-none',
|
||||||
|
(header.column.columnDef.meta as TableColumnMeta | undefined)?.headerClass
|
||||||
|
)"
|
||||||
|
@click="header.column.getToggleSortingHandler()?.($event)"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<FlexRender
|
||||||
|
v-if="!header.isPlaceholder"
|
||||||
|
:render="header.column.columnDef.header"
|
||||||
|
:props="header.getContext()"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
v-if="header.column.getCanSort()"
|
||||||
|
class="text-[10px] uppercase tracking-wide text-gray-400"
|
||||||
|
>
|
||||||
|
{{ header.column.getIsSorted() === 'asc' ? 'asc' : header.column.getIsSorted() === 'desc' ? 'desc' : '' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
<template v-if="loading">
|
||||||
|
<tr v-if="$slots.loading">
|
||||||
|
<td
|
||||||
|
:colspan="columns.length || 1"
|
||||||
|
class="px-4 py-10 text-center text-sm text-gray-500"
|
||||||
|
>
|
||||||
|
<slot name="loading" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr
|
||||||
|
v-for="rowIndex in skeletonRowIndexes"
|
||||||
|
v-else
|
||||||
|
:key="`skeleton-row-${rowIndex}`"
|
||||||
|
class="border-b border-gray-200 last:border-b-0"
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
v-for="columnIndex in skeletonColumnIndexes"
|
||||||
|
:key="`skeleton-cell-${rowIndex}-${columnIndex}`"
|
||||||
|
class="px-4 py-3 align-middle"
|
||||||
|
>
|
||||||
|
<div class="animate-pulse space-y-2">
|
||||||
|
<div
|
||||||
|
:class="cn(
|
||||||
|
'h-4 rounded bg-muted/50',
|
||||||
|
columnIndex === skeletonColumnIndexes.length - 1
|
||||||
|
? 'ml-auto w-16'
|
||||||
|
: 'w-full max-w-[12rem]'
|
||||||
|
)"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-if="columnIndex === 0"
|
||||||
|
class="h-3 w-24 rounded bg-muted/40"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<tr v-else-if="!table.getRowModel().rows.length">
|
||||||
|
<td
|
||||||
|
:colspan="columns.length || 1"
|
||||||
|
class="px-4 py-10 text-center text-sm text-gray-500"
|
||||||
|
>
|
||||||
|
<slot name="empty">
|
||||||
|
{{ emptyText }}
|
||||||
|
</slot>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr
|
||||||
|
v-for="row in table.getRowModel().rows"
|
||||||
|
v-else
|
||||||
|
:key="row.id"
|
||||||
|
:class="cn(
|
||||||
|
'border-b border-gray-200 transition-colors last:border-b-0 hover:bg-gray-50',
|
||||||
|
resolveBodyRowClass(row)
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
v-for="cell in row.getVisibleCells()"
|
||||||
|
:key="cell.id"
|
||||||
|
:class="cn(
|
||||||
|
'px-4 py-3 align-middle',
|
||||||
|
(cell.column.columnDef.meta as TableColumnMeta | undefined)?.cellClass
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<FlexRender
|
||||||
|
:render="cell.column.columnDef.cell"
|
||||||
|
:props="cell.getContext()"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div v-if="shouldRenderPagination" class="flex flex-col gap-3 border-t border-gray-200 bg-muted/20 px-6 py-4 text-xs text-foreground/55 sm:flex-row sm:items-center sm:justify-between">
|
||||||
|
<div>{{ t('common.page', { current: currentPage, total: totalPages }) }} · {{ totalRecords }} {{ t('common.records') }}</div>
|
||||||
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
|
<label v-if="pageSizeOptions.length" class="flex items-center gap-2">
|
||||||
|
<span>{{ t('common.rowsPerPage') }}</span>
|
||||||
|
<select class="rounded-md border border-border bg-background px-2 py-1 text-xs text-foreground" :value="String(rowsPerPage)" @change="changePageSize">
|
||||||
|
<option v-for="option in pageSizeOptions" :key="option" :value="String(option)">{{ option }}</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<div class="flex items-center gap-2 xl:justify-end">
|
||||||
|
<AppButton size="sm" variant="secondary" :disabled="!canPreviousPage" @click="previousPage">{{ t('common.previous') }}</AppButton>
|
||||||
|
<AppButton size="sm" variant="secondary" :disabled="!canNextPage" @click="nextPage">{{ t('common.next') }}</AppButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
58
src/composables/useAdminRuntimeMqtt.ts
Normal file
58
src/composables/useAdminRuntimeMqtt.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import { TinyMqttClient } from "@/lib/liteMqtt";
|
||||||
|
import { useAuthStore } from "@/stores/auth";
|
||||||
|
import { computed, onBeforeUnmount, watch } from "vue";
|
||||||
|
|
||||||
|
type RuntimeMessage = {
|
||||||
|
topic: string;
|
||||||
|
payload: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
const mqttBrokerUrl = "wss://mqtt-dashboard.com:8884/mqtt";
|
||||||
|
|
||||||
|
export function useAdminRuntimeMqtt(onMessage: (message: RuntimeMessage) => void) {
|
||||||
|
const auth = useAuthStore();
|
||||||
|
let client: TinyMqttClient | undefined;
|
||||||
|
|
||||||
|
const isAdmin = computed(() => auth.user?.role?.toUpperCase?.() === "ADMIN");
|
||||||
|
|
||||||
|
const connect = () => {
|
||||||
|
if (import.meta.env.SSR || !isAdmin.value) return;
|
||||||
|
disconnect();
|
||||||
|
client = new TinyMqttClient(
|
||||||
|
mqttBrokerUrl,
|
||||||
|
["picpic/events", "picpic/logs/#", "picpic/job/+"],
|
||||||
|
(topic, raw) => {
|
||||||
|
try {
|
||||||
|
onMessage({ topic, payload: JSON.parse(raw) });
|
||||||
|
} catch {
|
||||||
|
onMessage({ topic, payload: raw });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
client.connect();
|
||||||
|
};
|
||||||
|
|
||||||
|
const disconnect = () => {
|
||||||
|
client?.disconnect();
|
||||||
|
client = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopWatch = watch(
|
||||||
|
() => [auth.user?.id, auth.user?.role],
|
||||||
|
() => {
|
||||||
|
if (isAdmin.value) {
|
||||||
|
connect();
|
||||||
|
} else {
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
stopWatch();
|
||||||
|
disconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
return { disconnect };
|
||||||
|
}
|
||||||
@@ -30,12 +30,16 @@ const state = reactive<AppConfirmState>({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const requireConfirm = (options: AppConfirmOptions) => {
|
const requireConfirm = (options: AppConfirmOptions) => {
|
||||||
|
const defaultHeader = 'Confirm';
|
||||||
|
const defaultAccept = 'OK';
|
||||||
|
const defaultReject = 'Cancel';
|
||||||
|
|
||||||
state.visible = true;
|
state.visible = true;
|
||||||
state.loading = false;
|
state.loading = false;
|
||||||
state.message = options.message;
|
state.message = options.message;
|
||||||
state.header = options.header ?? 'Confirm';
|
state.header = options.header ?? defaultHeader;
|
||||||
state.acceptLabel = options.acceptLabel ?? 'OK';
|
state.acceptLabel = options.acceptLabel ?? defaultAccept;
|
||||||
state.rejectLabel = options.rejectLabel ?? 'Cancel';
|
state.rejectLabel = options.rejectLabel ?? defaultReject;
|
||||||
state.accept = options.accept;
|
state.accept = options.accept;
|
||||||
state.reject = options.reject;
|
state.reject = options.reject;
|
||||||
};
|
};
|
||||||
|
|||||||
47
src/composables/useNetworkStatus.ts
Normal file
47
src/composables/useNetworkStatus.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
const isOffline = ref(false)
|
||||||
|
|
||||||
|
let listenersCount = 0
|
||||||
|
|
||||||
|
function syncNetworkStatus() {
|
||||||
|
if (typeof navigator === 'undefined') return
|
||||||
|
|
||||||
|
isOffline.value = !navigator.onLine
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleNetworkStatusChange() {
|
||||||
|
syncNetworkStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
function startListening() {
|
||||||
|
if (typeof window === 'undefined') return
|
||||||
|
|
||||||
|
if (listenersCount === 0) {
|
||||||
|
syncNetworkStatus()
|
||||||
|
window.addEventListener('online', handleNetworkStatusChange)
|
||||||
|
window.addEventListener('offline', handleNetworkStatusChange)
|
||||||
|
}
|
||||||
|
|
||||||
|
listenersCount += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopListening() {
|
||||||
|
if (typeof window === 'undefined' || listenersCount === 0) return
|
||||||
|
|
||||||
|
listenersCount -= 1
|
||||||
|
|
||||||
|
if (listenersCount === 0) {
|
||||||
|
window.removeEventListener('online', handleNetworkStatusChange)
|
||||||
|
window.removeEventListener('offline', handleNetworkStatusChange)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useNetworkStatus() {
|
||||||
|
return {
|
||||||
|
isOffline,
|
||||||
|
syncNetworkStatus,
|
||||||
|
startListening,
|
||||||
|
stopListening,
|
||||||
|
}
|
||||||
|
}
|
||||||
166
src/composables/useNotifications.ts
Normal file
166
src/composables/useNotifications.ts
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
import { client as rpcClient } from '@/api/rpcclient';
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
|
||||||
|
export type NotificationType = 'info' | 'success' | 'warning' | 'error' | 'video' | 'payment' | 'system';
|
||||||
|
|
||||||
|
export type AppNotification = {
|
||||||
|
id: string;
|
||||||
|
type: NotificationType;
|
||||||
|
title: string;
|
||||||
|
message: string;
|
||||||
|
time: string;
|
||||||
|
read: boolean;
|
||||||
|
actionUrl?: string;
|
||||||
|
actionLabel?: string;
|
||||||
|
createdAt?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type NotificationApiItem = {
|
||||||
|
id?: string;
|
||||||
|
type?: string;
|
||||||
|
title?: string;
|
||||||
|
message?: string;
|
||||||
|
read?: boolean;
|
||||||
|
actionUrl?: string;
|
||||||
|
actionLabel?: string;
|
||||||
|
createdAt?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type IncomingNotificationEnvelope = {
|
||||||
|
type?: string;
|
||||||
|
payload?: NotificationApiItem;
|
||||||
|
};
|
||||||
|
|
||||||
|
const notifications = ref<AppNotification[]>([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const loaded = ref(false);
|
||||||
|
|
||||||
|
const normalizeType = (value?: string): NotificationType => {
|
||||||
|
switch ((value || '').toLowerCase()) {
|
||||||
|
case 'video':
|
||||||
|
case 'payment':
|
||||||
|
case 'warning':
|
||||||
|
case 'error':
|
||||||
|
case 'success':
|
||||||
|
case 'system':
|
||||||
|
return value as NotificationType;
|
||||||
|
default:
|
||||||
|
return 'info';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapNotification = (item: NotificationApiItem): AppNotification => ({
|
||||||
|
id: item.id || '',
|
||||||
|
type: normalizeType(item.type),
|
||||||
|
title: item.title || '',
|
||||||
|
message: item.message || '',
|
||||||
|
time: '',
|
||||||
|
read: Boolean(item.read),
|
||||||
|
actionUrl: item.actionUrl || undefined,
|
||||||
|
actionLabel: item.actionLabel || undefined,
|
||||||
|
createdAt: item.createdAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
const upsertNotification = (item: NotificationApiItem) => {
|
||||||
|
const mapped = mapNotification({ ...item, read: item.read ?? false });
|
||||||
|
if (!mapped.id) return;
|
||||||
|
|
||||||
|
const index = notifications.value.findIndex(notification => notification.id === mapped.id);
|
||||||
|
if (index >= 0) {
|
||||||
|
notifications.value[index] = { ...notifications.value[index], ...mapped };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
notifications.value = [mapped, ...notifications.value];
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useNotifications() {
|
||||||
|
const { t, i18next } = useTranslation();
|
||||||
|
|
||||||
|
const formatRelativeTime = (value?: string) => {
|
||||||
|
if (!value) return '';
|
||||||
|
const date = new Date(value);
|
||||||
|
if (Number.isNaN(date.getTime())) return '';
|
||||||
|
|
||||||
|
const diffMs = Date.now() - date.getTime();
|
||||||
|
const minutes = Math.max(1, Math.floor(diffMs / 60000));
|
||||||
|
if (minutes < 60) return t('notification.time.minutesAgo', { count: minutes });
|
||||||
|
const hours = Math.floor(minutes / 60);
|
||||||
|
if (hours < 24) return t('notification.time.hoursAgo', { count: hours });
|
||||||
|
const days = Math.floor(hours / 24);
|
||||||
|
return t('notification.time.daysAgo', { count: Math.max(1, days) });
|
||||||
|
};
|
||||||
|
|
||||||
|
const hydrateNotification = (item: NotificationApiItem): AppNotification => ({
|
||||||
|
...mapNotification(item),
|
||||||
|
time: formatRelativeTime(item.createdAt),
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetchNotifications = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const response = await rpcClient.listNotifications();
|
||||||
|
notifications.value = (response.notifications || []).map(hydrateNotification);
|
||||||
|
loaded.value = true;
|
||||||
|
return notifications.value;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ingestRealtimeNotification = (raw: string | IncomingNotificationEnvelope) => {
|
||||||
|
try {
|
||||||
|
|
||||||
|
const envelope = typeof raw === 'string' ? JSON.parse(raw) as IncomingNotificationEnvelope : raw;
|
||||||
|
if (envelope?.type !== 'notification.created' || !envelope.payload) return false;
|
||||||
|
upsertNotification(envelope.payload);
|
||||||
|
notifications.value = notifications.value.map(item => ({
|
||||||
|
...item,
|
||||||
|
time: formatRelativeTime(item.createdAt),
|
||||||
|
}));
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const markRead = async (id: string) => {
|
||||||
|
if (!id) return;
|
||||||
|
await rpcClient.markNotificationRead({ id });
|
||||||
|
const item = notifications.value.find(notification => notification.id === id);
|
||||||
|
if (item) item.read = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteNotification = async (id: string) => {
|
||||||
|
if (!id) return;
|
||||||
|
await rpcClient.deleteNotification({ id });
|
||||||
|
notifications.value = notifications.value.filter(notification => notification.id !== id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const markAllRead = async () => {
|
||||||
|
await rpcClient.markAllNotificationsRead();
|
||||||
|
notifications.value = notifications.value.map(item => ({ ...item, read: true }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearAll = async () => {
|
||||||
|
await rpcClient.clearNotifications();
|
||||||
|
notifications.value = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const unreadCount = computed(() => notifications.value.filter(item => !item.read).length);
|
||||||
|
|
||||||
|
return {
|
||||||
|
notifications,
|
||||||
|
loading,
|
||||||
|
loaded,
|
||||||
|
unreadCount,
|
||||||
|
locale: computed(() => i18next.resolvedLanguage),
|
||||||
|
fetchNotifications,
|
||||||
|
ingestRealtimeNotification,
|
||||||
|
markRead,
|
||||||
|
deleteNotification,
|
||||||
|
markAllRead,
|
||||||
|
clearAll,
|
||||||
|
};
|
||||||
|
}
|
||||||
59
src/composables/useRouteLoading.ts
Normal file
59
src/composables/useRouteLoading.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
const visible = ref(false)
|
||||||
|
const progress = ref(0)
|
||||||
|
|
||||||
|
let timer: ReturnType<typeof setInterval> | null = null
|
||||||
|
|
||||||
|
function start() {
|
||||||
|
if (timer) clearInterval(timer)
|
||||||
|
|
||||||
|
visible.value = true
|
||||||
|
progress.value = 8
|
||||||
|
|
||||||
|
timer = setInterval(() => {
|
||||||
|
if (progress.value < 80) {
|
||||||
|
progress.value += Math.random() * 12
|
||||||
|
} else if (progress.value < 95) {
|
||||||
|
progress.value += Math.random() * 3
|
||||||
|
}
|
||||||
|
}, 200)
|
||||||
|
}
|
||||||
|
|
||||||
|
function finish() {
|
||||||
|
if (timer) {
|
||||||
|
clearInterval(timer)
|
||||||
|
timer = null
|
||||||
|
}
|
||||||
|
|
||||||
|
progress.value = 100
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
visible.value = false
|
||||||
|
progress.value = 0
|
||||||
|
}, 250)
|
||||||
|
}
|
||||||
|
|
||||||
|
function fail() {
|
||||||
|
if (timer) {
|
||||||
|
clearInterval(timer)
|
||||||
|
timer = null
|
||||||
|
}
|
||||||
|
|
||||||
|
progress.value = 100
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
visible.value = false
|
||||||
|
progress.value = 0
|
||||||
|
}, 250)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useRouteLoading() {
|
||||||
|
return {
|
||||||
|
visible,
|
||||||
|
progress,
|
||||||
|
start,
|
||||||
|
finish,
|
||||||
|
fail,
|
||||||
|
}
|
||||||
|
}
|
||||||
76
src/composables/useSettingsPreferencesQuery.ts
Normal file
76
src/composables/useSettingsPreferencesQuery.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import { client as rpcClient } from '@/api/rpcclient';
|
||||||
|
import type { Preferences } from '@/server/api/proto/app/v1/common';
|
||||||
|
import type { UpdatePreferencesRequest } from '@/server/api/proto/app/v1/account';
|
||||||
|
import { useQuery } from '@pinia/colada';
|
||||||
|
|
||||||
|
export const SETTINGS_PREFERENCES_QUERY_KEY = ['settings', 'preferences'] as const;
|
||||||
|
|
||||||
|
export type SettingsPreferencesSnapshot = {
|
||||||
|
emailNotifications: boolean;
|
||||||
|
pushNotifications: boolean;
|
||||||
|
marketingNotifications: boolean;
|
||||||
|
telegramNotifications: boolean;
|
||||||
|
language: string;
|
||||||
|
locale: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type NotificationSettingsDraft = {
|
||||||
|
email: boolean;
|
||||||
|
push: boolean;
|
||||||
|
marketing: boolean;
|
||||||
|
telegram: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type PreferencesResponse = {
|
||||||
|
preferences?: Preferences;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEFAULT_SETTINGS_PREFERENCES_SNAPSHOT: SettingsPreferencesSnapshot = {
|
||||||
|
emailNotifications: true,
|
||||||
|
pushNotifications: true,
|
||||||
|
marketingNotifications: false,
|
||||||
|
telegramNotifications: false,
|
||||||
|
language: 'en',
|
||||||
|
locale: 'en',
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalizePreferencesSnapshot = (responseData: unknown): SettingsPreferencesSnapshot => {
|
||||||
|
const preferences = (responseData as PreferencesResponse | undefined)?.preferences;
|
||||||
|
|
||||||
|
return {
|
||||||
|
emailNotifications: preferences?.emailNotifications ?? DEFAULT_SETTINGS_PREFERENCES_SNAPSHOT.emailNotifications,
|
||||||
|
pushNotifications: preferences?.pushNotifications ?? DEFAULT_SETTINGS_PREFERENCES_SNAPSHOT.pushNotifications,
|
||||||
|
marketingNotifications: preferences?.marketingNotifications ?? DEFAULT_SETTINGS_PREFERENCES_SNAPSHOT.marketingNotifications,
|
||||||
|
telegramNotifications: preferences?.telegramNotifications ?? DEFAULT_SETTINGS_PREFERENCES_SNAPSHOT.telegramNotifications,
|
||||||
|
language: preferences?.language ?? DEFAULT_SETTINGS_PREFERENCES_SNAPSHOT.language,
|
||||||
|
locale: preferences?.locale ?? DEFAULT_SETTINGS_PREFERENCES_SNAPSHOT.locale,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createNotificationSettingsDraft = (
|
||||||
|
snapshot: SettingsPreferencesSnapshot = DEFAULT_SETTINGS_PREFERENCES_SNAPSHOT,
|
||||||
|
): NotificationSettingsDraft => ({
|
||||||
|
email: snapshot.emailNotifications,
|
||||||
|
push: snapshot.pushNotifications,
|
||||||
|
marketing: snapshot.marketingNotifications,
|
||||||
|
telegram: snapshot.telegramNotifications,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const toNotificationPreferencesPayload = (
|
||||||
|
draft: NotificationSettingsDraft,
|
||||||
|
): UpdatePreferencesRequest => ({
|
||||||
|
emailNotifications: draft.email,
|
||||||
|
pushNotifications: draft.push,
|
||||||
|
marketingNotifications: draft.marketing,
|
||||||
|
telegramNotifications: draft.telegram,
|
||||||
|
});
|
||||||
|
|
||||||
|
export function useSettingsPreferencesQuery() {
|
||||||
|
return useQuery({
|
||||||
|
key: () => SETTINGS_PREFERENCES_QUERY_KEY,
|
||||||
|
query: async () => {
|
||||||
|
const response = await rpcClient.getPreferences();
|
||||||
|
return normalizePreferencesSnapshot(response);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { client } from '@/api/rpcclient';
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
export interface QueueItem {
|
export interface QueueItem {
|
||||||
@@ -282,22 +283,19 @@ export function useUploadQueue() {
|
|||||||
if (!item.file || !item.uploadedUrls) return;
|
if (!item.file || !item.uploadedUrls) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/merge', {
|
const data = await client.merge(item.file.name, item.uploadedUrls, item.file.size);
|
||||||
method: 'POST',
|
// const response = await fetch('/merge', {
|
||||||
headers: { 'Content-Type': 'application/json' },
|
// method: 'POST',
|
||||||
body: JSON.stringify({
|
// headers: { 'Content-Type': 'application/json' },
|
||||||
filename: item.file.name,
|
// body: JSON.stringify({
|
||||||
chunks: item.uploadedUrls,
|
// filename: item.file.name,
|
||||||
size: item.file.size
|
// chunks: item.uploadedUrls,
|
||||||
})
|
// size: item.file.size
|
||||||
});
|
// })
|
||||||
|
// });
|
||||||
const data = await response.json();
|
if (!data) {
|
||||||
|
throw new Error('No response from server');
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(data.error || 'Merge failed');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
item.status = 'complete';
|
item.status = 'complete';
|
||||||
item.progress = 100;
|
item.progress = 100;
|
||||||
item.uploaded = item.total;
|
item.uploaded = item.total;
|
||||||
|
|||||||
38
src/composables/useUsageQuery.ts
Normal file
38
src/composables/useUsageQuery.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { client as rpcClient } from '@/api/rpcclient';
|
||||||
|
import { useQuery } from '@pinia/colada';
|
||||||
|
|
||||||
|
export const USAGE_QUERY_KEY = ['usage'] as const;
|
||||||
|
|
||||||
|
export type UsageSnapshot = {
|
||||||
|
totalVideos: number;
|
||||||
|
totalStorage: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type UsageResponse = {
|
||||||
|
totalVideos?: number;
|
||||||
|
totalStorage?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEFAULT_USAGE_SNAPSHOT: UsageSnapshot = {
|
||||||
|
totalVideos: 0,
|
||||||
|
totalStorage: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalizeUsageSnapshot = (responseData: unknown): UsageSnapshot => {
|
||||||
|
const usage = responseData as UsageResponse | undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalVideos: usage?.totalVideos ?? DEFAULT_USAGE_SNAPSHOT.totalVideos,
|
||||||
|
totalStorage: usage?.totalStorage ?? DEFAULT_USAGE_SNAPSHOT.totalStorage,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useUsageQuery() {
|
||||||
|
return useQuery({
|
||||||
|
key: () => USAGE_QUERY_KEY,
|
||||||
|
query: async () => {
|
||||||
|
const response = await rpcClient.getUsage();
|
||||||
|
return normalizeUsageSnapshot(response);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
7
src/i18n/constants.ts
Normal file
7
src/i18n/constants.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export const supportedLocales = ['en', 'vi'] as const;
|
||||||
|
|
||||||
|
export type SupportedLocale = (typeof supportedLocales)[number];
|
||||||
|
|
||||||
|
export const defaultLocale: SupportedLocale = 'en';
|
||||||
|
|
||||||
|
export const localeCookieKey = 'i18next';
|
||||||
@@ -1,26 +1,19 @@
|
|||||||
import { Hono } from 'hono';
|
import { Hono } from 'hono';
|
||||||
|
|
||||||
import { apiProxyMiddleware } from './server/middlewares/apiProxy';
|
|
||||||
import { setupMiddlewares } from './server/middlewares/setup';
|
import { setupMiddlewares } from './server/middlewares/setup';
|
||||||
import { registerDisplayRoutes } from './server/routes/display';
|
import { registerAuthRoutes } from './server/routes/auth';
|
||||||
import { registerManifestRoutes } from './server/routes/manifest';
|
import { registerRpcRoutes } from './server/routes/rpc';
|
||||||
import { registerMergeRoutes } from './server/routes/merge';
|
|
||||||
import { registerSSRRoutes } from './server/routes/ssr';
|
import { registerSSRRoutes } from './server/routes/ssr';
|
||||||
import { registerWellKnownRoutes } from './server/routes/wellKnown';
|
import { registerWellKnownRoutes } from './server/routes/wellKnown';
|
||||||
|
import { setupServices } from './server/services/grpcClient';
|
||||||
const app = new Hono();
|
const app = new Hono();
|
||||||
|
|
||||||
// Global middlewares
|
// Global middlewares
|
||||||
setupMiddlewares(app);
|
setupMiddlewares(app);
|
||||||
|
setupServices(app);
|
||||||
// API proxy middleware (handles /r/*)
|
|
||||||
app.use(apiProxyMiddleware);
|
|
||||||
|
|
||||||
// Routes
|
// Routes
|
||||||
registerWellKnownRoutes(app);
|
registerWellKnownRoutes(app);
|
||||||
registerMergeRoutes(app);
|
registerAuthRoutes(app);
|
||||||
registerDisplayRoutes(app);
|
registerRpcRoutes(app);
|
||||||
registerManifestRoutes(app);
|
|
||||||
registerSSRRoutes(app);
|
registerSSRRoutes(app);
|
||||||
|
|
||||||
export default app;
|
export default app;
|
||||||
|
|||||||
@@ -85,7 +85,9 @@ export class TinyMqttClient implements ITinyMqttClient {
|
|||||||
break;
|
break;
|
||||||
case 0xD0: // PINGRESP
|
case 0xD0: // PINGRESP
|
||||||
break;
|
break;
|
||||||
case 0x30: // PUBLISH
|
case 0x30: // PUBLISH QoS 0
|
||||||
|
case 0x32: // PUBLISH QoS 1
|
||||||
|
case 0x34: // PUBLISH QoS 2
|
||||||
this.parsePublish(data);
|
this.parsePublish(data);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -102,9 +104,32 @@ export class TinyMqttClient implements ITinyMqttClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private parsePublish(data: Uint8Array): void {
|
private parsePublish(data: Uint8Array): void {
|
||||||
const tLen = (data[2] << 8) | data[3];
|
let multiplier = 1;
|
||||||
const topic = this.decoder.decode(data.slice(4, 4 + tLen));
|
let remainingLength = 0;
|
||||||
const payload = this.decoder.decode(data.slice(4 + tLen));
|
let offset = 1;
|
||||||
|
let encodedByte = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
encodedByte = data[offset++];
|
||||||
|
remainingLength += (encodedByte & 127) * multiplier;
|
||||||
|
multiplier *= 128;
|
||||||
|
} while ((encodedByte & 128) !== 0 && offset < data.length);
|
||||||
|
|
||||||
|
const variableHeaderStart = offset;
|
||||||
|
const topicLength = (data[offset] << 8) | data[offset + 1];
|
||||||
|
offset += 2;
|
||||||
|
|
||||||
|
const topic = this.decoder.decode(data.slice(offset, offset + topicLength));
|
||||||
|
offset += topicLength;
|
||||||
|
|
||||||
|
const qos = (data[0] >> 1) & 0x03;
|
||||||
|
if (qos > 0) {
|
||||||
|
offset += 2; // packet identifier
|
||||||
|
}
|
||||||
|
|
||||||
|
const consumedFromVariableHeader = offset - variableHeaderStart;
|
||||||
|
const payloadLength = Math.max(0, remainingLength - consumedFromVariableHeader);
|
||||||
|
const payload = this.decoder.decode(data.slice(offset, offset + payloadLength));
|
||||||
this.onMessage(topic, payload);
|
this.onMessage(topic, payload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
42
src/lib/translation/index.ts
Normal file
42
src/lib/translation/index.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import i18next from "i18next";
|
||||||
|
import I18NextHttpBackend, { HttpBackendOptions } from "i18next-http-backend";
|
||||||
|
const backendOptions: HttpBackendOptions = {
|
||||||
|
loadPath: 'http://localhost:5173/locales/{{lng}}/{{ns}}.json',
|
||||||
|
request: (_options, url, _payload, callback) => {
|
||||||
|
fetch(url)
|
||||||
|
.then((res) =>
|
||||||
|
res.json().then((r) => {
|
||||||
|
callback(null, {
|
||||||
|
data: JSON.stringify(r),
|
||||||
|
status: 200,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.catch(() => {
|
||||||
|
callback(null, {
|
||||||
|
status: 500,
|
||||||
|
data: '',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
export const createI18nInstance = (lng: string) => {
|
||||||
|
console.log('Initializing i18n with language:', lng);
|
||||||
|
const i18n = i18next.createInstance();
|
||||||
|
|
||||||
|
i18n
|
||||||
|
.use(I18NextHttpBackend)
|
||||||
|
.init({
|
||||||
|
lng,
|
||||||
|
supportedLngs: ["en", "vi"],
|
||||||
|
fallbackLng: "en",
|
||||||
|
defaultNS: "translation",
|
||||||
|
ns: ['translation'],
|
||||||
|
interpolation: {
|
||||||
|
escapeValue: false,
|
||||||
|
},
|
||||||
|
backend: backendOptions,
|
||||||
|
});
|
||||||
|
return i18n;
|
||||||
|
};
|
||||||
|
export default createI18nInstance;
|
||||||
113
src/lib/utils.ts
113
src/lib/utils.ts
@@ -5,7 +5,10 @@ import { twMerge } from "tailwind-merge";
|
|||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs));
|
return twMerge(clsx(inputs));
|
||||||
}
|
}
|
||||||
export function debounce<Func extends (...args: any[]) => any>(func: Func, wait: number): Func {
|
export function debounce<Func extends (...args: any[]) => any>(
|
||||||
|
func: Func,
|
||||||
|
wait: number
|
||||||
|
): Func {
|
||||||
let timeout: ReturnType<typeof setTimeout> | null;
|
let timeout: ReturnType<typeof setTimeout> | null;
|
||||||
return function (this: any, ...args: any[]) {
|
return function (this: any, ...args: any[]) {
|
||||||
if (timeout) clearTimeout(timeout);
|
if (timeout) clearTimeout(timeout);
|
||||||
@@ -17,8 +20,8 @@ export function debounce<Func extends (...args: any[]) => any>(func: Func, wait:
|
|||||||
type AspectInfo = {
|
type AspectInfo = {
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
ratio: string; // ví dụ: "16:9"
|
ratio: string; // ví dụ: "16:9"
|
||||||
float: number; // ví dụ: 1.777...
|
float: number; // ví dụ: 1.777...
|
||||||
};
|
};
|
||||||
|
|
||||||
function gcd(a: number, b: number): number {
|
function gcd(a: number, b: number): number {
|
||||||
@@ -39,7 +42,7 @@ export function getImageAspectRatio(url: string): Promise<AspectInfo> {
|
|||||||
width: w,
|
width: w,
|
||||||
height: h,
|
height: h,
|
||||||
ratio: `${w / g}:${h / g}`,
|
ratio: `${w / g}:${h / g}`,
|
||||||
float: w / h
|
float: w / h,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -49,48 +52,100 @@ export function getImageAspectRatio(url: string): Promise<AspectInfo> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const formatBytes = (bytes?: number) => {
|
export const formatBytes = (bytes?: number) => {
|
||||||
if (!bytes) return '0 B';
|
if (!bytes) return "0 B";
|
||||||
const k = 1024;
|
const k = 1024;
|
||||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
const sizes = ["B", "KB", "MB", "GB", "TB"];
|
||||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
const value = parseFloat((bytes / Math.pow(k, i)).toFixed(2));
|
||||||
|
return `${value} ${sizes[i]}`;
|
||||||
|
// return `${new Intl.NumberFormat(getRuntimeLocaleTag()).format(value)} ${sizes[i]}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const formatDuration = (seconds?: number) => {
|
export const formatDuration = (seconds?: number) => {
|
||||||
if (!seconds) return '0:00';
|
if (!seconds) return "0:00";
|
||||||
const h = Math.floor(seconds / 3600);
|
const h = Math.floor(seconds / 3600);
|
||||||
const m = Math.floor((seconds % 3600) / 60);
|
const m = Math.floor((seconds % 3600) / 60);
|
||||||
const s = Math.floor(seconds % 60);
|
const s = Math.floor(seconds % 60);
|
||||||
|
|
||||||
if (h > 0) {
|
if (h > 0) {
|
||||||
return `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
|
return `${h}:${m.toString().padStart(2, "0")}:${s
|
||||||
|
.toString()
|
||||||
|
.padStart(2, "0")}`;
|
||||||
}
|
}
|
||||||
return `${m}:${s.toString().padStart(2, '0')}`;
|
return `${m}:${s.toString().padStart(2, "0")}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const formatDate = (dateString: string = "", dateOnly: boolean = false) => {
|
export const formatDate = (
|
||||||
if (!dateString) return '';
|
dateString: string = "",
|
||||||
return new Date(dateString).toLocaleDateString('en-US', {
|
dateOnly: boolean = false
|
||||||
month: 'short',
|
) => {
|
||||||
day: 'numeric',
|
if (!dateString) return "";
|
||||||
year: 'numeric',
|
const locale =
|
||||||
...(dateOnly ? {} : { hour: '2-digit', minute: '2-digit' })
|
typeof document !== "undefined"
|
||||||
|
? document.documentElement.lang === "vi"
|
||||||
|
? "vi-VN"
|
||||||
|
: "en-US"
|
||||||
|
: "en-US";
|
||||||
|
return new Date(dateString).toLocaleDateString(locale, {
|
||||||
|
month: "short",
|
||||||
|
day: "numeric",
|
||||||
|
year: "numeric",
|
||||||
|
...(dateOnly ? {} : { hour: "2-digit", minute: "2-digit" }),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
type Status = "success" | "failed" | "pending" | string;
|
||||||
export const getStatusSeverity = (status: string = "") => {
|
export const getStatusSeverity = (status: Status = "") => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'success':
|
case "success":
|
||||||
case 'ready':
|
case "ready":
|
||||||
return 'success';
|
return "success";
|
||||||
case 'failed':
|
case "failed":
|
||||||
return 'danger';
|
return "danger";
|
||||||
case 'pending':
|
case "pending":
|
||||||
return 'warn';
|
return "warn";
|
||||||
default:
|
default:
|
||||||
return 'info';
|
return "info";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
export const getStatusStyles = (status: Status = "") => {
|
||||||
|
switch (status) {
|
||||||
|
case "success":
|
||||||
|
return "bg-success/10 text-success";
|
||||||
|
case "failed":
|
||||||
|
return "bg-danger/10 text-danger";
|
||||||
|
case "pending":
|
||||||
|
return "bg-warning/10 text-warning";
|
||||||
|
default:
|
||||||
|
return "bg-info/10 text-info";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export const isAdmin = (role: string = "") => {
|
||||||
|
const r = String(role).toLowerCase();
|
||||||
|
return r === "admin" || r === "superadmin";
|
||||||
|
};
|
||||||
|
export type ApiErrorPayload = {
|
||||||
|
code?: number;
|
||||||
|
message?: string;
|
||||||
|
data?: Record<string, any>;
|
||||||
|
};
|
||||||
|
export const getApiErrorPayload = (error: unknown): ApiErrorPayload | null => {
|
||||||
|
if (!error || typeof error !== "object") return null;
|
||||||
|
const candidate = error as {
|
||||||
|
error?: ApiErrorPayload;
|
||||||
|
data?: ApiErrorPayload;
|
||||||
|
message?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (candidate.error && typeof candidate.error === "object")
|
||||||
|
return candidate.error;
|
||||||
|
if (candidate.data && typeof candidate.data === "object")
|
||||||
|
return candidate.data;
|
||||||
|
if (candidate.message) return { message: candidate.message };
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
export const getApiErrorMessage = (error: unknown, fallback: string) => {
|
||||||
|
const payload = getApiErrorPayload(error);
|
||||||
|
return payload?.message || fallback;
|
||||||
|
};
|
||||||
|
|||||||
41
src/main.ts
41
src/main.ts
@@ -1,17 +1,29 @@
|
|||||||
import { PiniaColada, useQueryCache } from '@pinia/colada';
|
import { PiniaColada, useQueryCache } from '@pinia/colada';
|
||||||
import { createHead as CSRHead } from "@unhead/vue/client";
|
import { createHead as CSRHead } from '@unhead/vue/client';
|
||||||
import { createHead as SSRHead } from "@unhead/vue/server";
|
import { createHead as SSRHead } from '@unhead/vue/server';
|
||||||
import { createPinia } from "pinia";
|
import { createPinia } from 'pinia';
|
||||||
import { createSSRApp } from 'vue';
|
import { createSSRApp } from 'vue';
|
||||||
import { RouterView } from 'vue-router';
|
import { RouterView } from 'vue-router';
|
||||||
|
|
||||||
|
import I18NextVue from 'i18next-vue';
|
||||||
|
|
||||||
import { withErrorBoundary } from './lib/hoc/withErrorBoundary';
|
import { withErrorBoundary } from './lib/hoc/withErrorBoundary';
|
||||||
|
import createI18nInstance from './lib/translation';
|
||||||
import createAppRouter from './routes';
|
import createAppRouter from './routes';
|
||||||
|
|
||||||
const bodyClass = ":uno: font-sans text-gray-800 antialiased flex flex-col min-h-screen"
|
const bodyClass = ':uno: font-sans text-gray-800 antialiased flex flex-col min-h-screen';
|
||||||
export function createApp() {
|
|
||||||
|
const getSerializedAppData = () => {
|
||||||
|
if (typeof document === 'undefined') return {} as Record<string, any>;
|
||||||
|
return JSON.parse(document.getElementById('__APP_DATA__')?.innerText || '{}') as Record<string, any>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function createApp(lng: string = 'en') {
|
||||||
const pinia = createPinia();
|
const pinia = createPinia();
|
||||||
const app = createSSRApp(withErrorBoundary(RouterView));
|
const app = createSSRApp(withErrorBoundary(RouterView));
|
||||||
|
|
||||||
const head = import.meta.env.SSR ? SSRHead() : CSRHead();
|
const head = import.meta.env.SSR ? SSRHead() : CSRHead();
|
||||||
|
const appData = !import.meta.env.SSR ? getSerializedAppData() : ({} as Record<string, any>);
|
||||||
|
|
||||||
app.use(head);
|
app.use(head);
|
||||||
app.directive('nh', {
|
app.directive('nh', {
|
||||||
@@ -20,11 +32,12 @@ export function createApp() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
app.use(pinia);
|
app.use(pinia);
|
||||||
|
app.use(I18NextVue, { i18next: createI18nInstance(lng) });
|
||||||
app.use(PiniaColada, {
|
app.use(PiniaColada, {
|
||||||
pinia,
|
pinia,
|
||||||
plugins: [
|
plugins: [
|
||||||
(context) => {
|
() => {
|
||||||
// console.log("PiniaColada plugin initialized for store:", context);
|
// reserved for query plugins
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
queryOptions: {
|
queryOptions: {
|
||||||
@@ -32,19 +45,21 @@ export function createApp() {
|
|||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
ssrCatchError: true,
|
ssrCatchError: true,
|
||||||
}
|
}
|
||||||
// optional options
|
});
|
||||||
})
|
|
||||||
// app.use(vueSWR({ revalidateOnFocus: false }));
|
|
||||||
const queryCache = useQueryCache();
|
const queryCache = useQueryCache();
|
||||||
const router = createAppRouter();
|
|
||||||
app.use(router);
|
|
||||||
if (!import.meta.env.SSR) {
|
if (!import.meta.env.SSR) {
|
||||||
Object.entries(JSON.parse(document.getElementById("__APP_DATA__")?.innerText || "{}")).forEach(([key, value]) => {
|
Object.entries(appData).forEach(([key, value]) => {
|
||||||
(window as any)[key] = value;
|
(window as any)[key] = value;
|
||||||
});
|
});
|
||||||
if ((window as any).$p) {
|
if ((window as any).$p) {
|
||||||
pinia.state.value = (window as any).$p;
|
pinia.state.value = (window as any).$p;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const router = createAppRouter();
|
||||||
|
app.use(router);
|
||||||
|
|
||||||
return { app, router, head, pinia, bodyClass, queryCache };
|
return { app, router, head, pinia, bodyClass, queryCache };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { ModelVideo } from "@/api/client";
|
import type { Video as ModelVideo } from "@/server/api/proto/app/v1/common";
|
||||||
|
|
||||||
export const mockVideos: ModelVideo[] = [
|
export const mockVideos: ModelVideo[] = [
|
||||||
{
|
{
|
||||||
@@ -9,7 +9,7 @@ export const mockVideos: ModelVideo[] = [
|
|||||||
duration: 345, // 5m 45s
|
duration: 345, // 5m 45s
|
||||||
status: 'ready',
|
status: 'ready',
|
||||||
size: 1024 * 1024 * 45, // 45MB
|
size: 1024 * 1024 * 45, // 45MB
|
||||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 24 * 2).toISOString(), // 2 days ago
|
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 2).toISOString(), // 2 days ago
|
||||||
views: 12500,
|
views: 12500,
|
||||||
url: '#'
|
url: '#'
|
||||||
},
|
},
|
||||||
@@ -20,9 +20,8 @@ export const mockVideos: ModelVideo[] = [
|
|||||||
thumbnail: 'https://picsum.photos/seed/video2/640/360',
|
thumbnail: 'https://picsum.photos/seed/video2/640/360',
|
||||||
duration: 890, // 14m 50s
|
duration: 890, // 14m 50s
|
||||||
status: 'processing',
|
status: 'processing',
|
||||||
processing_status: '75%',
|
|
||||||
size: 1024 * 1024 * 128, // 128MB
|
size: 1024 * 1024 * 128, // 128MB
|
||||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 5).toISOString(), // 5 hours ago
|
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 5).toISOString(), // 5 hours ago
|
||||||
views: 0,
|
views: 0,
|
||||||
url: '#'
|
url: '#'
|
||||||
},
|
},
|
||||||
@@ -34,7 +33,7 @@ export const mockVideos: ModelVideo[] = [
|
|||||||
duration: 120, // 2m 00s
|
duration: 120, // 2m 00s
|
||||||
status: 'ready',
|
status: 'ready',
|
||||||
size: 1024 * 1024 * 25, // 25MB
|
size: 1024 * 1024 * 25, // 25MB
|
||||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 24 * 7).toISOString(), // 1 week ago
|
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 7).toISOString(), // 1 week ago
|
||||||
views: 340,
|
views: 340,
|
||||||
url: '#'
|
url: '#'
|
||||||
},
|
},
|
||||||
@@ -46,7 +45,7 @@ export const mockVideos: ModelVideo[] = [
|
|||||||
duration: 1800, // 30m 00s
|
duration: 1800, // 30m 00s
|
||||||
status: 'ready',
|
status: 'ready',
|
||||||
size: 1024 * 1024 * 350, // 350MB
|
size: 1024 * 1024 * 350, // 350MB
|
||||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 24 * 14).toISOString(), // 2 weeks ago
|
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 14).toISOString(), // 2 weeks ago
|
||||||
views: 12,
|
views: 12,
|
||||||
url: '#'
|
url: '#'
|
||||||
},
|
},
|
||||||
@@ -58,7 +57,7 @@ export const mockVideos: ModelVideo[] = [
|
|||||||
duration: 600, // 10m 00s
|
duration: 600, // 10m 00s
|
||||||
status: 'failed',
|
status: 'failed',
|
||||||
size: 1024 * 1024 * 80, // 80MB
|
size: 1024 * 1024 * 80, // 80MB
|
||||||
created_at: new Date(Date.now() - 1000 * 60 * 30).toISOString(), // 30 mins ago
|
createdAt: new Date(Date.now() - 1000 * 60 * 30).toISOString(), // 30 mins ago
|
||||||
views: 0,
|
views: 0,
|
||||||
url: '#'
|
url: '#'
|
||||||
},
|
},
|
||||||
@@ -70,7 +69,7 @@ export const mockVideos: ModelVideo[] = [
|
|||||||
duration: 5400, // 1h 30m
|
duration: 5400, // 1h 30m
|
||||||
status: 'ready',
|
status: 'ready',
|
||||||
size: 1024 * 1024 * 1024 * 2.5, // 2.5GB
|
size: 1024 * 1024 * 1024 * 2.5, // 2.5GB
|
||||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 24 * 30).toISOString(), // 1 month ago
|
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 30).toISOString(), // 1 month ago
|
||||||
views: 45000,
|
views: 45000,
|
||||||
url: '#'
|
url: '#'
|
||||||
},
|
},
|
||||||
@@ -82,7 +81,7 @@ export const mockVideos: ModelVideo[] = [
|
|||||||
duration: 1540,
|
duration: 1540,
|
||||||
status: 'ready',
|
status: 'ready',
|
||||||
size: 1024 * 1024 * 200,
|
size: 1024 * 1024 * 200,
|
||||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 24 * 3).toISOString(),
|
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 3).toISOString(),
|
||||||
views: 8900,
|
views: 8900,
|
||||||
url: '#'
|
url: '#'
|
||||||
},
|
},
|
||||||
@@ -94,7 +93,7 @@ export const mockVideos: ModelVideo[] = [
|
|||||||
duration: 3200,
|
duration: 3200,
|
||||||
status: 'ready',
|
status: 'ready',
|
||||||
size: 1024 * 1024 * 800,
|
size: 1024 * 1024 * 800,
|
||||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||||
views: 1500,
|
views: 1500,
|
||||||
url: '#'
|
url: '#'
|
||||||
},
|
},
|
||||||
@@ -106,7 +105,7 @@ export const mockVideos: ModelVideo[] = [
|
|||||||
duration: 3200,
|
duration: 3200,
|
||||||
status: 'ready',
|
status: 'ready',
|
||||||
size: 1024 * 1024 * 800,
|
size: 1024 * 1024 * 800,
|
||||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||||
views: 1500,
|
views: 1500,
|
||||||
url: '#'
|
url: '#'
|
||||||
},
|
},
|
||||||
@@ -118,7 +117,7 @@ export const mockVideos: ModelVideo[] = [
|
|||||||
duration: 3200,
|
duration: 3200,
|
||||||
status: 'ready',
|
status: 'ready',
|
||||||
size: 1024 * 1024 * 800,
|
size: 1024 * 1024 * 800,
|
||||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||||
views: 1500,
|
views: 1500,
|
||||||
url: '#'
|
url: '#'
|
||||||
},
|
},
|
||||||
@@ -130,7 +129,7 @@ export const mockVideos: ModelVideo[] = [
|
|||||||
duration: 3200,
|
duration: 3200,
|
||||||
status: 'ready',
|
status: 'ready',
|
||||||
size: 1024 * 1024 * 800,
|
size: 1024 * 1024 * 800,
|
||||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||||
views: 1500,
|
views: 1500,
|
||||||
url: '#'
|
url: '#'
|
||||||
},
|
},
|
||||||
@@ -142,7 +141,7 @@ export const mockVideos: ModelVideo[] = [
|
|||||||
duration: 3200,
|
duration: 3200,
|
||||||
status: 'ready',
|
status: 'ready',
|
||||||
size: 1024 * 1024 * 800,
|
size: 1024 * 1024 * 800,
|
||||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||||
views: 1500,
|
views: 1500,
|
||||||
url: '#'
|
url: '#'
|
||||||
},
|
},
|
||||||
@@ -154,7 +153,7 @@ export const mockVideos: ModelVideo[] = [
|
|||||||
duration: 3200,
|
duration: 3200,
|
||||||
status: 'ready',
|
status: 'ready',
|
||||||
size: 1024 * 1024 * 800,
|
size: 1024 * 1024 * 800,
|
||||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||||
views: 1500,
|
views: 1500,
|
||||||
url: '#'
|
url: '#'
|
||||||
},
|
},
|
||||||
@@ -166,7 +165,7 @@ export const mockVideos: ModelVideo[] = [
|
|||||||
duration: 3200,
|
duration: 3200,
|
||||||
status: 'ready',
|
status: 'ready',
|
||||||
size: 1024 * 1024 * 800,
|
size: 1024 * 1024 * 800,
|
||||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||||
views: 1500,
|
views: 1500,
|
||||||
url: '#'
|
url: '#'
|
||||||
},
|
},
|
||||||
@@ -178,7 +177,7 @@ export const mockVideos: ModelVideo[] = [
|
|||||||
duration: 3200,
|
duration: 3200,
|
||||||
status: 'ready',
|
status: 'ready',
|
||||||
size: 1024 * 1024 * 800,
|
size: 1024 * 1024 * 800,
|
||||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||||
views: 1500,
|
views: 1500,
|
||||||
url: '#'
|
url: '#'
|
||||||
},
|
},
|
||||||
@@ -190,7 +189,7 @@ export const mockVideos: ModelVideo[] = [
|
|||||||
duration: 3200,
|
duration: 3200,
|
||||||
status: 'ready',
|
status: 'ready',
|
||||||
size: 1024 * 1024 * 800,
|
size: 1024 * 1024 * 800,
|
||||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||||
views: 1500,
|
views: 1500,
|
||||||
url: '#'
|
url: '#'
|
||||||
},
|
},
|
||||||
@@ -202,7 +201,7 @@ export const mockVideos: ModelVideo[] = [
|
|||||||
duration: 3200,
|
duration: 3200,
|
||||||
status: 'ready',
|
status: 'ready',
|
||||||
size: 1024 * 1024 * 800,
|
size: 1024 * 1024 * 800,
|
||||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||||
views: 1500,
|
views: 1500,
|
||||||
url: '#'
|
url: '#'
|
||||||
},
|
},
|
||||||
@@ -214,7 +213,7 @@ export const mockVideos: ModelVideo[] = [
|
|||||||
duration: 3200,
|
duration: 3200,
|
||||||
status: 'ready',
|
status: 'ready',
|
||||||
size: 1024 * 1024 * 800,
|
size: 1024 * 1024 * 800,
|
||||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||||
views: 1500,
|
views: 1500,
|
||||||
url: '#'
|
url: '#'
|
||||||
},
|
},
|
||||||
@@ -226,7 +225,7 @@ export const mockVideos: ModelVideo[] = [
|
|||||||
duration: 3200,
|
duration: 3200,
|
||||||
status: 'ready',
|
status: 'ready',
|
||||||
size: 1024 * 1024 * 800,
|
size: 1024 * 1024 * 800,
|
||||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||||
views: 1500,
|
views: 1500,
|
||||||
url: '#'
|
url: '#'
|
||||||
},
|
},
|
||||||
@@ -238,7 +237,7 @@ export const mockVideos: ModelVideo[] = [
|
|||||||
duration: 3200,
|
duration: 3200,
|
||||||
status: 'ready',
|
status: 'ready',
|
||||||
size: 1024 * 1024 * 800,
|
size: 1024 * 1024 * 800,
|
||||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||||
views: 1500,
|
views: 1500,
|
||||||
url: '#'
|
url: '#'
|
||||||
},
|
},
|
||||||
@@ -250,7 +249,7 @@ export const mockVideos: ModelVideo[] = [
|
|||||||
duration: 3200,
|
duration: 3200,
|
||||||
status: 'ready',
|
status: 'ready',
|
||||||
size: 1024 * 1024 * 800,
|
size: 1024 * 1024 * 800,
|
||||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||||
views: 1500,
|
views: 1500,
|
||||||
url: '#'
|
url: '#'
|
||||||
},
|
},
|
||||||
@@ -262,7 +261,7 @@ export const mockVideos: ModelVideo[] = [
|
|||||||
duration: 3200,
|
duration: 3200,
|
||||||
status: 'ready',
|
status: 'ready',
|
||||||
size: 1024 * 1024 * 800,
|
size: 1024 * 1024 * 800,
|
||||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||||
views: 1500,
|
views: 1500,
|
||||||
url: '#'
|
url: '#'
|
||||||
},
|
},
|
||||||
@@ -274,7 +273,7 @@ export const mockVideos: ModelVideo[] = [
|
|||||||
duration: 3200,
|
duration: 3200,
|
||||||
status: 'ready',
|
status: 'ready',
|
||||||
size: 1024 * 1024 * 800,
|
size: 1024 * 1024 * 800,
|
||||||
created_at: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||||
views: 1500,
|
views: 1500,
|
||||||
url: '#'
|
url: '#'
|
||||||
},
|
},
|
||||||
@@ -340,7 +339,7 @@ export const updateMockVideo = async (id: string, updates: { title: string; desc
|
|||||||
...mockVideos[videoIndex],
|
...mockVideos[videoIndex],
|
||||||
title: updates.title,
|
title: updates.title,
|
||||||
description: updates.description,
|
description: updates.description,
|
||||||
updated_at: new Date().toISOString()
|
updatedAt: new Date().toISOString()
|
||||||
};
|
};
|
||||||
return mockVideos[videoIndex];
|
return mockVideos[videoIndex];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<vue-head :input="{title: '404 - Page Not Found'}"/>
|
<vue-head :input="{ title: t('notFound.headTitle') }" />
|
||||||
<div class="mx-auto text-center mt-20 flex flex-col items-center gap-4">
|
<div class="mx-auto text-center mt-20 flex flex-col items-center gap-4">
|
||||||
<h1>404 - Page Not Found</h1>
|
<h1>{{ t('notFound.title') }}</h1>
|
||||||
<p>The page you are looking for does not exist.</p>
|
<p>{{ t('notFound.description') }}</p>
|
||||||
<router-link class="btn btn-primary" to="/">Go back to Home</router-link>
|
<router-link class="btn btn-primary" to="/">{{ t('notFound.backHome') }}</router-link>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { VueHead } from "@/components/VueHead";
|
import { VueHead } from '@/components/VueHead';
|
||||||
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
</script>
|
</script>
|
||||||
22
src/routes/analytics/Analytics.vue
Normal file
22
src/routes/analytics/Analytics.vue
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<PageHeader title="Analytics"
|
||||||
|
description="Your streaming analytics will be displayed here."
|
||||||
|
:breadcrumbs="breadcrumbs" />
|
||||||
|
<div class="p-6 rounded-xl border border-gray-200">
|
||||||
|
<h2 class="text-lg font-semibold mb-4">Coming Soon</h2>
|
||||||
|
<p class="text-gray-600">We are working hard to bring you detailed analytics about your streams. Stay tuned!</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
|
||||||
|
const {t} = useTranslation()
|
||||||
|
const breadcrumbs = computed(() => [
|
||||||
|
{ label: t('pageHeader.dashboard'), to: '/overview' },
|
||||||
|
{ label: t('pageHeader.settings'), to: '/settings' },
|
||||||
|
{ label: "Analytics"}
|
||||||
|
// ...(currentItem.value ? [{ label: currentItem.value.label + (currentItem.value.value.includes("admin") ? " (Admin)" : "") }] : []),
|
||||||
|
]);
|
||||||
|
</script>
|
||||||
@@ -2,16 +2,16 @@
|
|||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<form @submit.prevent="onFormSubmit" class="flex flex-col gap-4 w-full">
|
<form @submit.prevent="onFormSubmit" class="flex flex-col gap-4 w-full">
|
||||||
<div class="text-sm text-gray-600 mb-2">
|
<div class="text-sm text-gray-600 mb-2">
|
||||||
Enter your email address and we'll send you a link to reset your password.
|
{{ t('auth.forgot.description') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<label for="email" class="text-sm font-medium text-gray-700">Email address</label>
|
<label for="email" class="text-sm font-medium text-gray-700">{{ t('auth.forgot.email') }}</label>
|
||||||
<AppInput id="email" v-model="form.email" type="email" placeholder="you@example.com" />
|
<AppInput id="email" v-model="form.email" type="email" :placeholder="t('auth.forgot.placeholders.email')" />
|
||||||
<p v-if="errors.email" class="text-xs text-red-500 mt-0.5">{{ errors.email }}</p>
|
<p v-if="errors.email" class="text-xs text-red-500 mt-0.5">{{ errors.email }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AppButton type="submit" class="w-full">Send Reset Link</AppButton>
|
<AppButton type="submit" class="w-full">{{ t('auth.forgot.sendResetLink') }}</AppButton>
|
||||||
|
|
||||||
<div class="text-center mt-2">
|
<div class="text-center mt-2">
|
||||||
<router-link to="/login" replace
|
<router-link to="/login" replace
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
d="M10 19l-7-7m0 0l7-7m-7 7h18"></path>
|
d="M10 19l-7-7m0 0l7-7m-7 7h18"></path>
|
||||||
</svg>
|
</svg>
|
||||||
Back to Sign in
|
{{ t('auth.forgot.backToSignIn') }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -28,12 +28,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { client } from '@/api/client';
|
import { client as rpcClient } from '@/api/rpcclient';
|
||||||
import { useAppToast } from '@/composables/useAppToast';
|
import { useAppToast } from '@/composables/useAppToast';
|
||||||
import { reactive } from 'vue';
|
import { reactive } from 'vue';
|
||||||
|
import { useTranslation } from 'i18next-vue';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
const toast = useAppToast();
|
const toast = useAppToast();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
email: ''
|
email: ''
|
||||||
@@ -42,7 +44,7 @@ const form = reactive({
|
|||||||
const errors = reactive<{ email?: string }>({});
|
const errors = reactive<{ email?: string }>({});
|
||||||
|
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
email: z.string().min(1, { message: 'Email is required.' }).email({ message: 'Invalid email address.' })
|
email: z.string().min(1, { message: t('auth.forgot.errors.emailRequired') }).email({ message: t('auth.forgot.errors.emailInvalid') })
|
||||||
});
|
});
|
||||||
|
|
||||||
const onFormSubmit = () => {
|
const onFormSubmit = () => {
|
||||||
@@ -57,12 +59,22 @@ const onFormSubmit = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
client.auth.forgotPasswordCreate({ email: form.email })
|
rpcClient.forgotPassword({ email: form.email })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
toast.add({ severity: 'success', summary: 'Success', detail: 'Reset link sent', life: 3000 });
|
toast.add({
|
||||||
|
severity: 'success',
|
||||||
|
summary: t('auth.forgot.toast.successSummary'),
|
||||||
|
detail: t('auth.forgot.toast.successDetail'),
|
||||||
|
life: 3000,
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
toast.add({ severity: 'error', summary: 'Error', detail: error.message || 'An error occurred', life: 3000 });
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: t('auth.forgot.toast.errorSummary'),
|
||||||
|
detail: error.message || t('auth.forgot.toast.errorDetail'),
|
||||||
|
life: 3000,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
69
src/routes/auth/google-finalize.vue
Normal file
69
src/routes/auth/google-finalize.vue
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex flex-col items-center gap-3 py-6 text-center">
|
||||||
|
<div class="i-svg-spinners-90-ring-with-bg h-10 w-10 text-blue-600"></div>
|
||||||
|
<p class="text-sm text-gray-600">{{ message }}</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useAppToast } from '@/composables/useAppToast';
|
||||||
|
import { useAuthStore } from '@/stores/auth';
|
||||||
|
import { computed, onMounted } from 'vue';
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
const auth = useAuthStore();
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
const toast = useAppToast();
|
||||||
|
|
||||||
|
const status = computed(() => String(route.query.status ?? 'error'));
|
||||||
|
const reason = computed(() => String(route.query.reason ?? 'google_login_failed'));
|
||||||
|
|
||||||
|
const reasonMessages: Record<string, string> = {
|
||||||
|
missing_code: 'Google did not return an authorization code.',
|
||||||
|
access_denied: 'Google login was cancelled.',
|
||||||
|
exchange_failed: 'Failed to verify your Google sign-in. Please try again.',
|
||||||
|
userinfo_failed: 'Failed to load your Google account information.',
|
||||||
|
userinfo_parse_failed: 'Failed to read your Google account information.',
|
||||||
|
missing_email: 'Your Google account did not provide an email address.',
|
||||||
|
create_user_failed: 'Failed to create your account.',
|
||||||
|
update_user_failed: 'Failed to update your account.',
|
||||||
|
reload_user_failed: 'Failed to finish signing you in.',
|
||||||
|
session_failed: 'Failed to create your sign-in session.',
|
||||||
|
fetch_me_failed: 'Signed in with Google, but failed to load your account.',
|
||||||
|
google_login_failed: 'Google login failed. Please try again.',
|
||||||
|
};
|
||||||
|
|
||||||
|
const errorMessage = computed(() => reasonMessages[reason.value] ?? reasonMessages.google_login_failed);
|
||||||
|
const message = computed(() => status.value === 'success' ? 'Signing you in with Google...' : errorMessage.value);
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
if (status.value !== 'success') {
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Google login failed',
|
||||||
|
detail: errorMessage.value,
|
||||||
|
life: 5000,
|
||||||
|
});
|
||||||
|
await router.replace({ name: 'login', query: { reason: reason.value } });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const user = await auth.fetchMe();
|
||||||
|
if (!user) {
|
||||||
|
throw new Error('missing_user');
|
||||||
|
}
|
||||||
|
|
||||||
|
await router.replace({ name: 'overview' });
|
||||||
|
} catch {
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Google login failed',
|
||||||
|
detail: 'Signed in with Google, but failed to load your account.',
|
||||||
|
life: 5000,
|
||||||
|
});
|
||||||
|
await router.replace({ name: 'login', query: { reason: 'fetch_me_failed' } });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -5,12 +5,12 @@
|
|||||||
class=":uno: w-full shadow-xl bg-white p-6 rounded-xl relative before:(content-[''] absolute inset-[-5px] translate-0 z-[-1] opacity-50 rounded-xl bg-[linear-gradient(135deg,var(--glow-stop-1)_0,var(--glow-stop-2)_25%,var(--glow-stop-3)_50%,var(--glow-stop-4)_75%,var(--glow-stop-5)_100%)] animate-[glow-enter-blur_1s_ease_.5s_both]) after:(content-[''] absolute inset-[-1px] translate-0 z-[-1] opacity-50 rounded-xl bg-[linear-gradient(135deg,transparent_0,transparent_34%,transparent_49%,#fff_57%,#fff_64%,var(--glow-stop-1)_66%,var(--glow-stop-2)_75%,var(--glow-stop-3)_83%,var(--glow-stop-4)_92%,var(--glow-stop-5)_100%)] bg-[length:300%_300%] bg-[position:0_0] bg-no-repeat transition-background-position duration-800 ease animate-[glow-enter-stroke_.5s_ease_.5s_both])">
|
class=":uno: w-full shadow-xl bg-white p-6 rounded-xl relative before:(content-[''] absolute inset-[-5px] translate-0 z-[-1] opacity-50 rounded-xl bg-[linear-gradient(135deg,var(--glow-stop-1)_0,var(--glow-stop-2)_25%,var(--glow-stop-3)_50%,var(--glow-stop-4)_75%,var(--glow-stop-5)_100%)] animate-[glow-enter-blur_1s_ease_.5s_both]) after:(content-[''] absolute inset-[-1px] translate-0 z-[-1] opacity-50 rounded-xl bg-[linear-gradient(135deg,transparent_0,transparent_34%,transparent_49%,#fff_57%,#fff_64%,var(--glow-stop-1)_66%,var(--glow-stop-2)_75%,var(--glow-stop-3)_83%,var(--glow-stop-4)_92%,var(--glow-stop-5)_100%)] bg-[length:300%_300%] bg-[position:0_0] bg-no-repeat transition-background-position duration-800 ease animate-[glow-enter-stroke_.5s_ease_.5s_both])">
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<h2 class="text-xl font-medium text-gray-900">
|
<h2 class="text-xl font-medium text-gray-900">
|
||||||
{{ content[route.name as keyof typeof content]?.title || '' }}
|
{{ content[route.name as keyof typeof content.value]?.title || '' }}
|
||||||
</h2>
|
</h2>
|
||||||
<vue-head :input="{
|
<vue-head :input="{
|
||||||
title: content[route.name as keyof typeof content]?.headTitle || 'Authentication',
|
title: content[route.name as keyof typeof content.value]?.headTitle || t('app.name'),
|
||||||
meta: [
|
meta: [
|
||||||
{ name: 'description', content: content[route.name as keyof typeof content]?.subtitle || '' }
|
{ name: 'description', content: content[route.name as keyof typeof content.value]?.subtitle || '' }
|
||||||
]
|
]
|
||||||
}" />
|
}" />
|
||||||
</div>
|
</div>
|
||||||
@@ -18,29 +18,38 @@
|
|||||||
</div>
|
</div>
|
||||||
<router-link to="/" class="inline-flex items-center justify-center w-6 h-6 mt-10 group w-full">
|
<router-link to="/" class="inline-flex items-center justify-center w-6 h-6 mt-10 group w-full">
|
||||||
<img class="w-6 h-6" src="/apple-touch-icon.png" alt="Logo" /> <span
|
<img class="w-6 h-6" src="/apple-touch-icon.png" alt="Logo" /> <span
|
||||||
class="text-[#6a6a6a] font-medium group-hover:text-gray-900">EcoStream</span>
|
class="text-[#6a6a6a] font-medium group-hover:text-gray-900">{{ t('app.name') }}</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
import { computed } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const content = {
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const content = computed(() => ({
|
||||||
login: {
|
login: {
|
||||||
headTitle: "Login to your account",
|
headTitle: t('auth.layout.login.headTitle'),
|
||||||
title: 'Sign in to your dashboard',
|
title: t('auth.layout.login.title'),
|
||||||
subtitle: 'Please enter your details to sign in.'
|
subtitle: t('auth.layout.login.subtitle')
|
||||||
},
|
},
|
||||||
signup: {
|
signup: {
|
||||||
headTitle: "Create your account",
|
headTitle: t('auth.layout.signup.headTitle'),
|
||||||
title: 'Create your account',
|
title: t('auth.layout.signup.title'),
|
||||||
subtitle: 'Please fill in the information to create your account.'
|
subtitle: t('auth.layout.signup.subtitle')
|
||||||
},
|
},
|
||||||
forgot: {
|
forgot: {
|
||||||
title: 'Forgot your password?',
|
title: t('auth.layout.forgot.title'),
|
||||||
subtitle: "Enter your email address and we'll send you a link to reset your password.",
|
subtitle: t('auth.layout.forgot.subtitle'),
|
||||||
headTitle: "Reset your password"
|
headTitle: t('auth.layout.forgot.headTitle')
|
||||||
|
},
|
||||||
|
'google-auth-finalize': {
|
||||||
|
title: 'Google sign in',
|
||||||
|
subtitle: 'Completing your Google sign in.',
|
||||||
|
headTitle: 'Google sign in - Holistream'
|
||||||
}
|
}
|
||||||
}
|
}));
|
||||||
</script>
|
</script>
|
||||||
@@ -2,17 +2,17 @@
|
|||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<form @submit.prevent="onFormSubmit" class="flex flex-col gap-4 w-full">
|
<form @submit.prevent="onFormSubmit" class="flex flex-col gap-4 w-full">
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<label for="email" class="text-sm font-medium text-gray-700">Email</label>
|
<label for="email" class="text-sm font-medium text-gray-700">{{ t('auth.login.email') }}</label>
|
||||||
<AppInput id="email" v-model="form.email" type="text" placeholder="Enter your email"
|
<AppInput id="email" v-model="form.email" type="text" :placeholder="t('auth.signup.placeholders.email')"
|
||||||
:disabled="auth.loading" />
|
:disabled="auth.loading" />
|
||||||
<p v-if="errors.email" class="text-xs text-red-500 mt-0.5">{{ errors.email }}</p>
|
<p v-if="errors.email" class="text-xs text-red-500 mt-0.5">{{ errors.email }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<label for="password" class="text-sm font-medium text-gray-700">Password</label>
|
<label for="password" class="text-sm font-medium text-gray-700">{{ t('auth.login.password') }}</label>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<AppInput id="password" v-model="form.password" :type="showPassword ? 'text' : 'password'"
|
<AppInput id="password" v-model="form.password" :type="showPassword ? 'text' : 'password'"
|
||||||
placeholder="Enter your password" :disabled="auth.loading" />
|
:placeholder="t('auth.signup.placeholders.password')" :disabled="auth.loading" />
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="absolute right-2 top-1/2 -translate-y-1/2 p-1 text-gray-400 hover:text-gray-600"
|
class="absolute right-2 top-1/2 -translate-y-1/2 p-1 text-gray-400 hover:text-gray-600"
|
||||||
@click="showPassword = !showPassword" tabindex="-1">
|
@click="showPassword = !showPassword" tabindex="-1">
|
||||||
@@ -31,22 +31,15 @@
|
|||||||
<p v-if="errors.password" class="text-xs text-red-500 mt-0.5">{{ errors.password }}</p>
|
<p v-if="errors.password" class="text-xs text-red-500 mt-0.5">{{ errors.password }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-end">
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<input id="remember-me" v-model="form.rememberMe" type="checkbox"
|
|
||||||
class="w-4 h-4 rounded border-gray-300 text-primary focus:ring-primary"
|
|
||||||
:disabled="auth.loading" />
|
|
||||||
<label for="remember-me" class="text-sm text-gray-900">Remember me</label>
|
|
||||||
</div>
|
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
<router-link to="/forgot"
|
<router-link to="/forgot"
|
||||||
class="text-blue-600 hover:text-blue-500 hover:underline">Forgot
|
class="text-blue-600 hover:text-blue-500 hover:underline">{{ t('auth.login.forgotPassword') }}</router-link>
|
||||||
password?</router-link>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AppButton type="submit" :loading="auth.loading" class="w-full">
|
<AppButton type="submit" :loading="auth.loading" class="w-full">
|
||||||
{{ auth.loading ? 'Signing in...' : 'Sign in' }}
|
{{ auth.loading ? `${t('common.loading')}...` : t('auth.login.signIn') }}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
|
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
@@ -54,7 +47,7 @@
|
|||||||
<div class="w-full border-t border-gray-300"></div>
|
<div class="w-full border-t border-gray-300"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="relative flex justify-center text-sm">
|
<div class="relative flex justify-center text-sm">
|
||||||
<span class="px-2 bg-white text-gray-500">Or continue with</span>
|
<span class="px-2 bg-white text-gray-500">{{ t('auth.login.google') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -64,13 +57,13 @@
|
|||||||
<path
|
<path
|
||||||
d="M12.545,10.239v3.821h5.445c-0.712,2.315-2.647,3.972-5.445,3.972c-3.332,0-6.033-2.701-6.033-6.032s2.701-6.032,6.033-6.032c1.498,0,2.866,0.549,3.921,1.453l2.814-2.814C17.503,2.988,15.139,2,12.545,2C7.021,2,2.543,6.477,2.543,12s4.478,10,10.002,10c8.396,0,10.249-7.85,9.426-11.748L12.545,10.239z" />
|
d="M12.545,10.239v3.821h5.445c-0.712,2.315-2.647,3.972-5.445,3.972c-3.332,0-6.033-2.701-6.033-6.032s2.701-6.032,6.033-6.032c1.498,0,2.866,0.549,3.921,1.453l2.814-2.814C17.503,2.988,15.139,2,12.545,2C7.021,2,2.543,6.477,2.543,12s4.478,10,10.002,10c8.396,0,10.249-7.85,9.426-11.748L12.545,10.239z" />
|
||||||
</svg>
|
</svg>
|
||||||
Google
|
{{ t('auth.login.google') }}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
<div class="mt-2 flex flex-col items-center justify-center gap-1 text-sm text-gray-600">
|
<div class="mt-2 flex flex-col items-center justify-center gap-1 text-sm text-gray-600">
|
||||||
<p class="text-center text-sm text-gray-600">
|
<p class="text-center text-sm text-gray-600">
|
||||||
Don't have an account?
|
{{ t('auth.login.noAccount') }}
|
||||||
<router-link to="/sign-up"
|
<router-link to="/sign-up"
|
||||||
class="font-medium text-blue-600 hover:text-blue-500 hover:underline">Sign up</router-link>
|
class="font-medium text-blue-600 hover:text-blue-500 hover:underline">{{ t('auth.login.signUp') }}</router-link>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -81,11 +74,13 @@
|
|||||||
import { useAuthStore } from '@/stores/auth';
|
import { useAuthStore } from '@/stores/auth';
|
||||||
import { useAppToast } from '@/composables/useAppToast';
|
import { useAppToast } from '@/composables/useAppToast';
|
||||||
import { reactive, ref } from 'vue';
|
import { reactive, ref } from 'vue';
|
||||||
|
import { useTranslation } from 'i18next-vue';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
const toast = useAppToast();
|
const toast = useAppToast();
|
||||||
const auth = useAuthStore();
|
const auth = useAuthStore();
|
||||||
const showPassword = ref(false);
|
const showPassword = ref(false);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
email: '',
|
email: '',
|
||||||
@@ -96,8 +91,8 @@ const form = reactive({
|
|||||||
const errors = reactive<{ email?: string; password?: string }>({});
|
const errors = reactive<{ email?: string; password?: string }>({});
|
||||||
|
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
email: z.string().min(1, { message: 'Email or username is required.' }),
|
email: z.string().min(1, { message: t('auth.login.errors.emailRequired') }),
|
||||||
password: z.string().min(1, { message: 'Password is required.' })
|
password: z.string().min(1, { message: t('auth.login.errors.passwordRequired') })
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(() => auth.error, (newError) => {
|
watch(() => auth.error, (newError) => {
|
||||||
|
|||||||
@@ -2,22 +2,22 @@
|
|||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<form @submit.prevent="onFormSubmit" class="flex flex-col gap-4 w-full">
|
<form @submit.prevent="onFormSubmit" class="flex flex-col gap-4 w-full">
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<label for="name" class="text-sm font-medium text-gray-700">Full Name</label>
|
<label for="name" class="text-sm font-medium text-gray-700">{{ t('auth.signup.fullName') }}</label>
|
||||||
<AppInput id="name" v-model="form.name" placeholder="John Doe" />
|
<AppInput id="name" v-model="form.name" :placeholder="t('auth.signup.placeholders.name')" />
|
||||||
<p v-if="errors.name" class="text-xs text-red-500 mt-0.5">{{ errors.name }}</p>
|
<p v-if="errors.name" class="text-xs text-red-500 mt-0.5">{{ errors.name }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<label for="email" class="text-sm font-medium text-gray-700">Email address</label>
|
<label for="email" class="text-sm font-medium text-gray-700">{{ t('auth.signup.email') }}</label>
|
||||||
<AppInput id="email" v-model="form.email" type="email" placeholder="you@example.com" />
|
<AppInput id="email" v-model="form.email" type="email" :placeholder="t('auth.signup.placeholders.email')" />
|
||||||
<p v-if="errors.email" class="text-xs text-red-500 mt-0.5">{{ errors.email }}</p>
|
<p v-if="errors.email" class="text-xs text-red-500 mt-0.5">{{ errors.email }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<label for="password" class="text-sm font-medium text-gray-700">Password</label>
|
<label for="password" class="text-sm font-medium text-gray-700">{{ t('auth.signup.password') }}</label>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<AppInput id="password" v-model="form.password" :type="showPassword ? 'text' : 'password'"
|
<AppInput id="password" v-model="form.password" :type="showPassword ? 'text' : 'password'"
|
||||||
placeholder="Create a password" />
|
:placeholder="t('auth.signup.placeholders.password')" />
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="absolute right-2 top-1/2 -translate-y-1/2 p-1 text-gray-400 hover:text-gray-600"
|
class="absolute right-2 top-1/2 -translate-y-1/2 p-1 text-gray-400 hover:text-gray-600"
|
||||||
@click="showPassword = !showPassword" tabindex="-1">
|
@click="showPassword = !showPassword" tabindex="-1">
|
||||||
@@ -33,16 +33,36 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<small class="text-gray-500">Must be at least 8 characters.</small>
|
<small class="text-gray-500">{{ t('auth.signup.passwordHint') }}</small>
|
||||||
<p v-if="errors.password" class="text-xs text-red-500 mt-0.5">{{ errors.password }}</p>
|
<p v-if="errors.password" class="text-xs text-red-500 mt-0.5">{{ errors.password }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AppButton type="submit" class="w-full">Create Account</AppButton>
|
<div v-if="refUsername" class="rounded-lg border border-blue-200 bg-blue-50 px-3 py-2 text-sm text-blue-700">
|
||||||
|
Signing up with referral: <span class="font-medium">@{{ refUsername }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<AppButton type="submit" class="w-full">{{ t('auth.signup.createAccount') }}</AppButton>
|
||||||
|
|
||||||
|
<div class="relative">
|
||||||
|
<div class="absolute inset-0 flex items-center">
|
||||||
|
<div class="w-full border-t border-gray-300"></div>
|
||||||
|
</div>
|
||||||
|
<div class="relative flex justify-center text-sm">
|
||||||
|
<span class="px-2 bg-white text-gray-500">{{ t('auth.login.google') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<AppButton type="button" variant="secondary" class="w-full flex items-center justify-center gap-2" @click="signupWithGoogle">
|
||||||
|
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path d="M12.545,10.239v3.821h5.445c-0.712,2.315-2.647,3.972-5.445,3.972c-3.332,0-6.033-2.701-6.033-6.032s2.701-6.032,6.033-6.032c1.498,0,2.866,0.549,3.921,1.453l2.814-2.814C17.503,2.988,15.139,2,12.545,2C7.021,2,2.543,6.477,2.543,12s4.478,10,10.002,10c8.396,0,10.249-7.85,9.426-11.748L12.545,10.239z" />
|
||||||
|
</svg>
|
||||||
|
Continue with Google
|
||||||
|
</AppButton>
|
||||||
|
|
||||||
<p class="mt-4 text-center text-sm text-gray-600">
|
<p class="mt-4 text-center text-sm text-gray-600">
|
||||||
Already have an account?
|
{{ t('auth.signup.alreadyHave') }}
|
||||||
<router-link to="/login"
|
<router-link to="/login"
|
||||||
class="font-medium text-blue-600 hover:text-blue-500 hover:underline">Sign in</router-link>
|
class="font-medium text-blue-600 hover:text-blue-500 hover:underline">{{ t('auth.signup.signIn') }}</router-link>
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -50,11 +70,16 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useAuthStore } from '@/stores/auth';
|
import { useAuthStore } from '@/stores/auth';
|
||||||
import { reactive, ref } from 'vue';
|
import { computed, reactive, ref } from 'vue';
|
||||||
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
const auth = useAuthStore();
|
const auth = useAuthStore();
|
||||||
|
const route = useRoute();
|
||||||
const showPassword = ref(false);
|
const showPassword = ref(false);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const refUsername = computed(() => String(route.query.ref || '').trim());
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
name: '',
|
name: '',
|
||||||
@@ -65,9 +90,9 @@ const form = reactive({
|
|||||||
const errors = reactive<{ name?: string; email?: string; password?: string }>({});
|
const errors = reactive<{ name?: string; email?: string; password?: string }>({});
|
||||||
|
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
name: z.string().min(1, { message: 'Name is required.' }),
|
name: z.string().min(1, { message: t('auth.signup.errors.nameRequired') }),
|
||||||
email: z.string().min(1, { message: 'Email is required.' }).email({ message: 'Invalid email address.' }),
|
email: z.string().min(1, { message: t('auth.signup.errors.emailRequired') }).email({ message: t('auth.signup.errors.emailInvalid') }),
|
||||||
password: z.string().min(8, { message: 'Password must be at least 8 characters.' })
|
password: z.string().min(8, { message: t('auth.signup.errors.passwordMin') })
|
||||||
});
|
});
|
||||||
|
|
||||||
const onFormSubmit = () => {
|
const onFormSubmit = () => {
|
||||||
@@ -84,6 +109,10 @@ const onFormSubmit = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auth.register(form.name, form.email, form.password);
|
auth.register(form.name, form.email, form.password, refUsername.value || undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
const signupWithGoogle = () => {
|
||||||
|
auth.loginWithGoogle(refUsername.value || undefined);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,231 +1,373 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const signalItems = computed(() => [
|
||||||
|
{ label: t('home.features.live.bitrate'), value: t('home.features.live.bitrateValue') },
|
||||||
|
{ label: t('home.features.live.fps'), value: t('home.features.live.fpsValue') },
|
||||||
|
{ label: t('home.features.live.latency'), value: t('home.features.live.latencyValue') },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const featurePills = computed(() => [
|
||||||
|
t('home.features.global.title'),
|
||||||
|
t('home.features.encoding.title'),
|
||||||
|
t('home.features.analytics.title'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const getFeatureList = (key: string): string[] => {
|
||||||
|
const localized = t(key, { returnObjects: true });
|
||||||
|
return Array.isArray(localized) ? localized.map((item) => String(item)) : [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const pricing = computed(() => ({
|
||||||
|
title: t('home.pricing.title'),
|
||||||
|
subtitle: t('home.pricing.subtitle'),
|
||||||
|
packs: [
|
||||||
|
{
|
||||||
|
name: t('home.pricing.hobby.name'),
|
||||||
|
price: '$0',
|
||||||
|
features: getFeatureList('home.pricing.hobby.features'),
|
||||||
|
buttonText: t('home.pricing.hobby.button'),
|
||||||
|
tag: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: t('home.pricing.pro.name'),
|
||||||
|
price: '$29',
|
||||||
|
features: getFeatureList('home.pricing.pro.features'),
|
||||||
|
buttonText: t('home.pricing.pro.button'),
|
||||||
|
tag: t('home.pricing.pro.tag'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: t('home.pricing.scale.name'),
|
||||||
|
price: '$99',
|
||||||
|
features: getFeatureList('home.pricing.scale.features'),
|
||||||
|
buttonText: t('home.pricing.scale.button'),
|
||||||
|
tag: t('home.pricing.scale.tag'),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
|
||||||
|
const featuredTag = computed(() => t('home.pricing.pro.tag'));
|
||||||
|
const scaleTag = computed(() => t('home.pricing.scale.tag'));
|
||||||
|
const isFeaturedPack = (tag: string) => tag === featuredTag.value;
|
||||||
|
const isScalePack = (tag: string) => tag === scaleTag.value;
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class=":m: relative pt-32 pb-20 lg:pt-48 lg:pb-32 overflow-hidden min-h-svh flex">
|
<div class="bg-white text-slate-900">
|
||||||
<!-- <div class="absolute inset-0 bg-grid-pattern opacity-[0.4] -z-10"></div> -->
|
<section class="relative overflow-hidden border-b border-slate-100 bg-gradient-to-b from-slate-50 via-white to-white">
|
||||||
<div
|
<div class="pointer-events-none absolute inset-0">
|
||||||
class=":m: absolute top-0 right-0 -translate-y-1/2 translate-x-1/2 w-[800px] h-[800px] bg-primary-light/40 rounded-full blur-3xl -z-10 mix-blend-multiply animate-pulse duration-1000">
|
<div class="absolute inset-0 opacity-60 bg-[linear-gradient(rgba(148,163,184,0.12)_1px,transparent_1px),linear-gradient(90deg,rgba(148,163,184,0.12)_1px,transparent_1px)] bg-[length:64px_64px] [mask-image:linear-gradient(to_bottom,rgba(0,0,0,0.55),transparent_78%)]"></div>
|
||||||
</div>
|
<div class="absolute inset-x-0 top-0 h-[28rem] bg-[radial-gradient(circle_at_top,rgba(20,167,75,0.12),transparent_58%)]"></div>
|
||||||
<div
|
<div class="absolute -left-16 top-28 h-56 w-56 rounded-full bg-primary/10 blur-3xl"></div>
|
||||||
class=":m: absolute bottom-0 left-0 translate-y-1/2 -translate-x-1/2 w-[600px] h-[600px] bg-teal-100/50 rounded-full blur-3xl -z-10 mix-blend-multiply">
|
<div class="absolute right-0 top-20 h-72 w-72 rounded-full bg-sky-100 blur-3xl"></div>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="max-w-7xl m-auto px-4 sm:px-6 lg:px-8 text-center">
|
|
||||||
<h1
|
|
||||||
class="text-5xl md:text-7xl font-extrabold tracking-tight text-slate-900 mb-6 leading-[1.1] animate-backwards">
|
|
||||||
Video infrastructure for <br>
|
|
||||||
<span class="text-gradient">modern internet.</span>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<p class="text-xl text-slate-500 max-w-2xl mx-auto mb-10 leading-relaxed animate-backwards delay-50">
|
|
||||||
Seamlessly host, encode, and stream video with our developer-first API.
|
|
||||||
Optimized for speed, built for scale.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="flex flex-col sm:flex-row justify-center gap-4">
|
|
||||||
<RouterLink to="/get-started" class="flex btn btn-success !rounded-xl !p-4 press-animated">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" viewBox="46 -286 524 580">
|
|
||||||
<path d="M56 284v-560L560 4 56 284z" fill="#fff" />
|
|
||||||
</svg>
|
|
||||||
Get Started
|
|
||||||
</RouterLink>
|
|
||||||
<RouterLink to="/docs" class="flex btn btn-outline-primary !rounded-xl">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="28" viewBox="0 0 596 468">
|
|
||||||
<path
|
|
||||||
d="M10 314c0-63 41-117 98-136-1-8-2-16-2-24 0-79 65-144 144-144 55 0 104 31 128 77 14-8 30-13 48-13 53 0 96 43 96 96 0 16-4 31-10 44 44 20 74 64 74 116 0 71-57 128-128 128H154c-79 0-144-64-144-144zm199-73c-9 9-9 25 0 34s25 9 34 0l31-31v102c0 13 11 24 24 24s24-11 24-24V244l31 31c9 9 25 9 34 0s9-25 0-34l-72-72c-10-9-25-9-34 0l-72 72z"
|
|
||||||
fill="#14a74b" />
|
|
||||||
<path
|
|
||||||
d="M281 169c9-9 25-9 34 0l72 72c9 9 9 25 0 34s-25 9-34 0l-31-31v102c0 13-11 24-24 24s-24-11-24-24V244l-31 31c-9 9-25 9-34 0s-9-25 0-34l72-72z"
|
|
||||||
fill="#fff" />
|
|
||||||
</svg>
|
|
||||||
Upload video
|
|
||||||
</RouterLink>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section id="features" class="py-24 bg-white">
|
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
||||||
<div class="mb-16 md:text-center max-w-3xl mx-auto">
|
|
||||||
<h2 class="text-3xl font-bold text-slate-900 mb-4">Everything you need to ship video</h2>
|
|
||||||
<p class="text-lg text-slate-500">Focus on building your product. We'll handle the complex video
|
|
||||||
infrastructure.</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
<div class="relative mx-auto max-w-7xl px-4 pb-18 pt-28 sm:px-6 lg:px-8 lg:pb-24 lg:pt-34">
|
||||||
<div
|
<div class="grid items-center gap-12 lg:grid-cols-[1.02fr_0.98fr] lg:gap-14">
|
||||||
class=":m: md:col-span-2 bg-slate-50 rounded-2xl p-8 border border-slate-100 hover:border-primary/60 transition-all group overflow-hidden relative">
|
<div>
|
||||||
<div class="relative z-10">
|
<div class="inline-flex items-center gap-2 rounded-full border border-primary/15 bg-white/90 px-4 py-2 text-xs font-semibold uppercase tracking-[0.22em] text-primary shadow-sm">
|
||||||
<div
|
<span class="h-2 w-2 rounded-full bg-primary"></span>
|
||||||
class="w-12 h-12 bg-white rounded-xl flex items-center justify-center mb-6 border border-slate-100">
|
{{ t('home.features.live.onAir') }}
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="532" viewBox="-8 -258 529 532">
|
|
||||||
<path
|
|
||||||
d="M342 32c-2 69-16 129-35 172-10 23-22 40-32 49-10 10-16 11-19 11h-1c-3 0-9-1-19-11-10-9-22-26-32-49-19-43-33-103-35-172h173zm169 0c-9 103-80 188-174 219 30-51 50-129 53-219h121zm-390 0c3 89 23 167 53 218C80 219 11 134 2 32h119zm53-266c-30 51-50 129-53 218H2c9-102 78-186 172-218zm82-14c3 0 9 1 19 11 10 9 22 26 32 50 19 42 33 102 35 171H169c3-69 16-129 35-171 10-24 22-41 32-50s16-11 19-11h1zm81 13c94 31 165 116 174 219H390c-3-90-23-168-53-219z"
|
|
||||||
fill="#059669" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-xl font-bold text-slate-900 mb-2">Global Edge Network</h3>
|
|
||||||
<p class="text-slate-500 max-w-md">Content delivered from 200+ PoPs worldwide. Automatic region
|
|
||||||
selection ensures the lowest latency for every viewer.</p>
|
|
||||||
</div>
|
|
||||||
<div class="absolute right-0 bottom-0 opacity-10 translate-x-1/4 translate-y-1/4">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="-10 -258 532 532">
|
|
||||||
<path
|
|
||||||
d="M464 8c0-19-3-38-8-56l-27-5c-8-2-15 2-19 9-6 11-19 17-31 13l-14-5c-8-2-17 0-22 5-4 4-4 10 0 14l33 33c5 5 8 12 8 19 0 12-8 23-20 26l-6 1c-3 1-6 5-6 9v12c0 13-4 27-13 38l-25 34c-6 8-16 13-26 13-18 0-32-14-32-32V88c0-9-7-16-16-16h-32c-26 0-48-22-48-48V-4c0-13 6-24 16-32l39-30c6-4 13-6 20-6 3 0 7 1 10 2l32 10c7 3 15 3 22 1l36-9c10-2 17-11 17-22 0-8-5-16-13-20l-29-15c-3-2-8-1-11 2l-4 4c-4 4-11 7-17 7-4 0-8-1-11-3l-15-7c-7-4-15-2-20 4l-13 17c-6 7-16 8-22 1-3-2-5-6-5-10v-41c0-6-1-11-4-16l-10-18C102-154 48-79 48 8c0 115 93 208 208 208S464 123 464 8zM0 8c0-141 115-256 256-256S512-133 512 8 397 264 256 264 0 149 0 8z"
|
|
||||||
fill="#1e3050" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class=":m: md:row-span-2 bg-slate-900 rounded-2xl p-8 text-white relative overflow-hidden group">
|
<h1 class="mt-7 max-w-4xl text-5xl font-bold leading-[1.02] tracking-tight text-slate-900 sm:text-6xl lg:text-7xl">
|
||||||
<div class=":m: absolute inset-0 bg-gradient-to-b from-slate-800/50 to-transparent"></div>
|
<span class="block">{{ t('home.hero.titleLine1') }}</span>
|
||||||
<div class="relative z-10">
|
<span class="mt-2 block bg-[linear-gradient(135deg,#0f172a_0%,#14a74b_55%,#0ea5e9_100%)] bg-clip-text text-transparent">
|
||||||
<div
|
{{ t('home.hero.titleLine2') }}
|
||||||
class=":m: w-12 h-12 bg-white/10 rounded-xl flex items-center justify-center mb-6 backdrop-blur-sm border border-white/10">
|
</span>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" viewBox="-10 -146 468 384">
|
</h1>
|
||||||
<path
|
|
||||||
d="M392-136c-31 0-56 25-56 56v280c0 16 13 28 28 28h28c31 0 56-25 56-56V-80c0-31-25-56-56-56zM168 4c0-31 25-56 56-56h28c16 0 28 13 28 28v224c0 16-12 28-28 28h-56c-15 0-28-12-28-28V4zM0 88c0-31 25-56 56-56h28c16 0 28 13 28 28v140c0 16-12 28-28 28H56c-31 0-56-25-56-56V88z"
|
<p class="mt-6 max-w-2xl text-lg leading-8 text-slate-600 lg:text-xl">
|
||||||
fill="#fff" />
|
{{ t('home.hero.subtitle') }}
|
||||||
</svg>
|
</p>
|
||||||
|
|
||||||
|
<div class="mt-9 flex flex-col gap-3 sm:flex-row">
|
||||||
|
<RouterLink to="/sign-up" class="btn btn-success !rounded-xl !px-6 !py-3.5 shadow-sm">
|
||||||
|
{{ t('home.hero.getStarted') }}
|
||||||
|
</RouterLink>
|
||||||
|
<RouterLink to="/login" class="btn btn-outline-primary !rounded-xl !px-6 !py-3.5">
|
||||||
|
{{ t('home.hero.uploadVideo') }}
|
||||||
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-xl font-bold mb-2">Live Streaming API</h3>
|
|
||||||
<p class="text-slate-400 text-sm leading-relaxed mb-8">Scale to millions of concurrent viewers
|
|
||||||
with ultra-low latency. RTMP ingest and HLS playback supported natively.</p>
|
|
||||||
|
|
||||||
<!-- Visual -->
|
<div class="mt-10 grid gap-4 sm:grid-cols-3">
|
||||||
<div
|
<div
|
||||||
class="bg-slate-800/50 rounded-lg p-4 border border-white/5 font-mono text-xs text-brand-300">
|
v-for="signal in signalItems"
|
||||||
<div class="flex justify-between items-center mb-3 border-b border-white/5 pb-2">
|
:key="signal.label"
|
||||||
<span class="text-slate-500">Live Status</span>
|
class="rounded-2xl border border-slate-200 bg-white px-5 py-4"
|
||||||
|
>
|
||||||
|
<p class="text-xs font-semibold uppercase tracking-[0.18em] text-slate-400">
|
||||||
|
{{ signal.label }}
|
||||||
|
</p>
|
||||||
|
<p class="mt-2 text-xl font-bold text-slate-900">
|
||||||
|
{{ signal.value }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="relative mx-auto w-full max-w-[36rem] lg:mr-0">
|
||||||
|
<div class="rounded-[2rem] border border-slate-200 bg-white p-5 shadow-[0_24px_70px_rgba(15,23,42,0.08)] sm:p-6">
|
||||||
|
<div class="flex items-center justify-between border-b border-slate-100 pb-4">
|
||||||
|
<div>
|
||||||
|
<p class="text-xs font-semibold uppercase tracking-[0.18em] text-slate-400">
|
||||||
|
{{ t('home.features.live.status') }}
|
||||||
|
</p>
|
||||||
|
<h2 class="mt-2 text-2xl font-bold tracking-tight text-slate-900">
|
||||||
|
{{ t('home.features.live.title') }}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<span class="inline-flex items-center gap-2 rounded-full bg-primary/10 px-3 py-1 text-xs font-semibold text-primary">
|
||||||
|
<span class="h-2 w-2 rounded-full bg-primary"></span>
|
||||||
|
{{ t('home.features.live.onAir') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="mt-5 text-sm leading-7 text-slate-600 sm:text-base">
|
||||||
|
{{ t('home.features.live.description') }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="mt-5 rounded-[1.5rem] bg-slate-50 p-4 sm:p-5">
|
||||||
|
<div class="space-y-3">
|
||||||
|
<div
|
||||||
|
v-for="signal in signalItems"
|
||||||
|
:key="signal.label"
|
||||||
|
class="flex items-center justify-between rounded-2xl border border-slate-200 bg-white px-4 py-3"
|
||||||
|
>
|
||||||
|
<span class="text-sm text-slate-500">{{ signal.label }}</span>
|
||||||
|
<span class="text-sm font-semibold text-slate-900">{{ signal.value }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-5 grid grid-cols-12 items-end gap-2">
|
||||||
|
<span
|
||||||
|
v-for="n in 12"
|
||||||
|
:key="n"
|
||||||
|
class="rounded-full bg-[linear-gradient(180deg,rgba(20,167,75,0.95),rgba(20,167,75,0.2))]"
|
||||||
|
:style="{ height: `${34 + (n % 5) * 12}px`, opacity: 0.4 + (n % 4) * 0.13 }"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-5 flex flex-wrap gap-2.5">
|
||||||
<span
|
<span
|
||||||
class=":m: flex items-center gap-1.5 text-red-500 text-[10px] uppercase font-bold tracking-wider animate-pulse"><span
|
v-for="pill in featurePills"
|
||||||
class="w-1.5 h-1.5 rounded-full bg-red-500 animate-pulse"></span> On Air</span>
|
:key="pill"
|
||||||
|
class="rounded-full border border-slate-200 bg-slate-50 px-3 py-1.5 text-xs font-medium text-slate-600"
|
||||||
|
>
|
||||||
|
{{ pill }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-1">
|
</div>
|
||||||
<div class="flex justify-between"><span class="text-slate-400">Bitrate:</span> <span
|
|
||||||
class="text-white">6000 kbps</span></div>
|
<div class="absolute -right-4 top-12 hidden w-56 rounded-2xl border border-slate-200 bg-white p-4 shadow-xl lg:block">
|
||||||
<div class="flex justify-between"><span class="text-slate-400">FPS:</span> <span
|
<p class="text-xs font-semibold uppercase tracking-[0.18em] text-slate-400">
|
||||||
class="text-white">60</span></div>
|
{{ t('home.features.global.title') }}
|
||||||
<div class="flex justify-between"><span class="text-slate-400">Latency:</span> <span
|
</p>
|
||||||
class="text-brand-400">~2s</span></div>
|
<p class="mt-3 text-sm leading-6 text-slate-600">
|
||||||
|
{{ t('home.features.global.description') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="absolute -left-4 bottom-8 hidden w-64 rounded-2xl border border-slate-200 bg-white p-4 shadow-xl lg:block">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<p class="text-sm font-semibold text-slate-900">
|
||||||
|
{{ t('home.features.analytics.title') }}
|
||||||
|
</p>
|
||||||
|
<span class="rounded-full bg-slate-100 px-2 py-1 text-[11px] font-medium text-slate-500">
|
||||||
|
{{ t('home.features.live.onAir') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p class="mt-2 text-sm leading-6 text-slate-600">
|
||||||
|
{{ t('home.features.analytics.description') }}
|
||||||
|
</p>
|
||||||
|
<div class="mt-4 grid grid-cols-4 items-end gap-2">
|
||||||
|
<span class="h-10 rounded-full bg-slate-200"></span>
|
||||||
|
<span class="h-18 rounded-full bg-primary/70"></span>
|
||||||
|
<span class="h-14 rounded-full bg-sky-300"></span>
|
||||||
|
<span class="h-22 rounded-full bg-slate-900"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<!-- Standard Feature -->
|
<section id="features" class="border-b border-slate-100 bg-slate-50/70 py-20">
|
||||||
<div
|
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
class=":m: bg-slate-50 rounded-2xl p-8 border border-slate-100 transition-all group hover:(border-brand-200 shadow-lg shadow-brand-500/5)">
|
<div class="mx-auto mb-14 max-w-3xl text-center">
|
||||||
<div
|
<p class="text-sm font-semibold uppercase tracking-[0.22em] text-primary">
|
||||||
class=":m: w-12 h-12 bg-white rounded-xl shadow-sm flex items-center justify-center mb-6 text-purple-600 border border-slate-100">
|
{{ t('home.features.heading') }}
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" viewBox="0 0 570 570">
|
</p>
|
||||||
<path
|
<h2 class="mt-4 text-4xl font-bold tracking-tight text-slate-900 sm:text-5xl">
|
||||||
d="M50 428c-5 5-5 14 0 19s14 5 19 0l237-237c5-5 5-14 0-19s-14-5-19 0L50 428zm16-224c-5 5-5 13 0 19 5 5 14 5 19 0l12-12c5-5 5-14 0-19-6-5-14-5-20 0l-11 12zM174 60c-5 5-5 13 0 19 5 5 14 5 19 0l12-12c5-5 5-14 0-19-6-5-14-5-20 0l-11 12zm215 29c-5 5-5 14 0 19s14 5 19 0l39-39c5-5 5-14 0-19s-14-5-19 0l-39 39zm21 357c-5 5-5 14 0 19s14 5 19 0l18-18c5-5 5-14 0-19s-14-5-19 0l-18 18z"
|
{{ t('home.features.heading') }}
|
||||||
fill="#a6acb9" />
|
</h2>
|
||||||
<path
|
<p class="mt-4 text-lg leading-8 text-slate-600">
|
||||||
d="M170 26c14-15 36-15 50 0l18 18c15 14 15 36 0 50l-18 18c-14 15-36 15-50 0l-18-18c-15-14-15-36 0-50l18-18zm35 41c5-5 5-14 0-19-6-5-14-5-20 0l-11 12c-5 5-5 13 0 19 5 5 14 5 19 0l12-12zm204 342c21-21 55-21 76 0l18 18c21 21 21 55 0 76l-18 18c-21 21-55 21-76 0l-18-18c-21-21-21-55 0-76l18-18zm38 38c5-5 5-14 0-19s-14-5-19 0l-18 18c-5 5-5 14 0 19s14 5 19 0l18-18zM113 170c-15-15-37-15-51 0l-18 18c-14 14-14 36 0 50l18 18c14 15 37 15 51 0l18-18c14-14 14-36 0-50l-18-18zm-16 41-12 12c-5 5-14 5-19 0-5-6-5-14 0-20l11-11c6-5 14-5 20 0 5 5 5 14 0 19zM485 31c-21-21-55-21-76 0l-39 39c-21 21-21 55 0 76l54 54c21 21 55 21 76 0l39-39c21-21 21-55 0-76l-54-54zm-38 38-39 39c-5 5-14 5-19 0s-5-14 0-19l39-39c5-5 14-5 19 0s5 14 0 19zm-49 233c21-21 21-55 0-76l-54-54c-21-21-55-21-76 0L31 409c-21 21-21 55 0 76l54 54c21 21 55 21 76 0l237-237zm-92-92L69 447c-5 5-14 5-19 0s-5-14 0-19l237-237c5-5 14-5 19 0s5 14 0 19z"
|
{{ t('home.features.subtitle') }}
|
||||||
fill="#1e3050" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<h3 class="text-xl font-bold text-slate-900 mb-2">Instant Encoding</h3>
|
|
||||||
<p class="text-slate-500 text-sm">Upload raw files and get optimized HLS/DASH streams in seconds.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Standard Feature -->
|
<div class="grid gap-6 lg:grid-cols-3">
|
||||||
<div
|
<article class="rounded-3xl border border-slate-200 bg-white p-8 transition-all duration-200 ease-out hover:-translate-y-1 hover:shadow-[0_18px_44px_rgba(15,23,42,0.08)]">
|
||||||
class=":m: bg-slate-50 rounded-2xl p-8 border border-slate-100 transition-all group hover:(border-brand-200 shadow-lg shadow-brand-500/5)">
|
<div class="inline-flex h-12 w-12 items-center justify-center rounded-2xl bg-primary/10 text-primary">
|
||||||
<div
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" viewBox="-8 -258 529 532" fill="none">
|
||||||
class=":m: w-12 h-12 bg-white rounded-xl shadow-sm flex items-center justify-center mb-6 text-orange-600 border border-slate-100">
|
<path
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" viewBox="-10 -226 532 468">
|
d="M342 32c-2 69-16 129-35 172-10 23-22 40-32 49-10 10-16 11-19 11h-1c-3 0-9-1-19-11-10-9-22-26-32-49-19-43-33-103-35-172h173zm169 0c-9 103-80 188-174 219 30-51 50-129 53-219h121zm-390 0c3 89 23 167 53 218C80 219 11 134 2 32h119zm53-266c-30 51-50 129-53 218H2c9-102 78-186 172-218zm82-14c3 0 9 1 19 11 10 9 22 26 32 50 19 42 33 102 35 171H169c3-69 16-129 35-171 10-24 22-41 32-50s16-11 19-11h1zm81 13c94 31 165 116 174 219H390c-3-90-23-168-53-219z"
|
||||||
<path
|
fill="currentColor"
|
||||||
d="M32-216c18 0 32 14 32 32v336c0 9 7 16 16 16h400c18 0 32 14 32 32s-14 32-32 32H80c-44 0-80-36-80-80v-336c0-18 14-32 32-32zM144-24c18 0 32 14 32 32v64c0 18-14 32-32 32s-32-14-32-32V8c0-18 14-32 32-32zm144-64V72c0 18-14 32-32 32s-32-14-32-32V-88c0-18 14-32 32-32s32 14 32 32zm80 32c18 0 32 14 32 32v96c0 18-14 32-32 32s-32-14-32-32v-96c0-18 14-32 32-32zm144-96V72c0 18-14 32-32 32s-32-14-32-32v-224c0-18 14-32 32-32s32 14 32 32z"
|
/>
|
||||||
fill="#1e3050" />
|
</svg>
|
||||||
</svg>
|
</div>
|
||||||
</div>
|
<h3 class="mt-5 text-2xl font-bold tracking-tight text-slate-900">
|
||||||
<h3 class="text-xl font-bold text-slate-900 mb-2">Deep Analytics</h3>
|
{{ t('home.features.global.title') }}
|
||||||
<p class="text-slate-500 text-sm">Session-level insights, quality of experience (QoE) metrics, and
|
</h3>
|
||||||
more.</p>
|
<p class="mt-3 text-base leading-7 text-slate-600">
|
||||||
</div>
|
{{ t('home.features.global.description') }}
|
||||||
</div>
|
</p>
|
||||||
</div>
|
</article>
|
||||||
</section>
|
|
||||||
<!-- Pricing -->
|
|
||||||
<section id="pricing" class="py-24 border-t border-slate-100 bg-white">
|
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
||||||
<div class="text-center mb-16">
|
|
||||||
<h2 class="text-3xl font-bold text-slate-900 mb-4">{{ pricing.title }}</h2>
|
|
||||||
<p class="text-slate-500">{{ pricing.subtitle }}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-8 w-full">
|
<article class="rounded-3xl border border-slate-200 bg-white p-8 transition-all duration-200 ease-out hover:-translate-y-1 hover:shadow-[0_18px_44px_rgba(15,23,42,0.08)]">
|
||||||
<div v-for="pack in pricing.packs" :key="pack.name"
|
<div class="inline-flex h-12 w-12 items-center justify-center rounded-2xl bg-violet-50 text-violet-600">
|
||||||
:class="cn(':uno: p-8 rounded-2xl relative overflow-hidden hover:border-primary transition-colors flex flex-col justify-between', pack.tag == 'POPULAR' ? 'border-primary/80 border-2' : 'border-slate-200 border')"
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" viewBox="0 0 570 570" fill="none">
|
||||||
:style="{ background: pack.bg }">
|
<path
|
||||||
<div v-if="pack.tag"
|
d="M50 428c-5 5-5 14 0 19s14 5 19 0l237-237c5-5 5-14 0-19s-14-5-19 0L50 428zm16-224c-5 5-5 13 0 19 5 5 14 5 19 0l12-12c5-5 5-14 0-19-6-5-14-5-20 0l-11 12zM174 60c-5 5-5 13 0 19 5 5 14 5 19 0l12-12c5-5 5-14 0-19-6-5-14-5-20 0l-11 12zm215 29c-5 5-5 14 0 19s14 5 19 0l39-39c5-5 5-14 0-19s-14-5-19 0l-39 39zm21 357c-5 5-5 14 0 19s14 5 19 0l18-18c5-5 5-14 0-19s-14-5-19 0l-18 18z"
|
||||||
class=":m: absolute top-0 right-0 bg-primary/80 text-white text-xs font-bold px-3 py-1 rounded-bl-lg uppercase">
|
fill="#a6acb9"
|
||||||
{{ pack.tag }}</div>
|
/>
|
||||||
<div>
|
<path
|
||||||
<h3 class="font-semibold text-slate-900 text-xl mb-2">{{ pack.name }}</h3>
|
d="M170 26c14-15 36-15 50 0l18 18c15 14 15 36 0 50l-18 18c-14 15-36 15-50 0l-18-18c-15-14-15-36 0-50l18-18zm35 41c5-5 5-14 0-19-6-5-14-5-20 0l-11 12c-5 5-5 13 0 19 5 5 14 5 19 0l12-12zm204 342c21-21 55-21 76 0l18 18c21 21 21 55 0 76l-18 18c-21 21-55 21-76 0l-18-18c-21-21-21-55 0-76l18-18zm38 38c5-5 5-14 0-19s-14-5-19 0l-18 18c-5 5-5 14 0 19s14 5 19 0l18-18zM113 170c-15-15-37-15-51 0l-18 18c-14 14-14 36 0 50l18 18c14 15 37 15 51 0l18-18c14-14 14-36 0-50l-18-18zm-16 41-12 12c-5 5-14 5-19 0-5-6-5-14 0-20l11-11c6-5 14-5 20 0 5 5 5 14 0 19zM485 31c-21-21-55-21-76 0l-39 39c-21 21-21 55 0 76l54 54c21 21 55 21 76 0l39-39c21-21 21-55 0-76l-54-54zm-38 38-39 39c-5 5-14 5-19 0s-5-14 0-19l39-39c5-5 14-5 19 0s5 14 0 19zm-49 233c21-21 21-55 0-76l-54-54c-21-21-55-21-76 0L31 409c-21 21-21 55 0 76l54 54c21 21 55 21 76 0l237-237zm-92-92L69 447c-5 5-14 5-19 0s-5-14 0-19l237-237c5-5 14-5 19 0s5 14 0 19z"
|
||||||
<div class="flex items-baseline gap-1 mb-6">
|
fill="currentColor"
|
||||||
<span class="text-4xl font-bold text-slate-900">{{ pack.price }}</span>
|
/>
|
||||||
<span class="text-slate-500">/mo</span>
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3 class="mt-5 text-2xl font-bold tracking-tight text-slate-900">
|
||||||
|
{{ t('home.features.encoding.title') }}
|
||||||
|
</h3>
|
||||||
|
<p class="mt-3 text-base leading-7 text-slate-600">
|
||||||
|
{{ t('home.features.encoding.description') }}
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="rounded-3xl border border-slate-200 bg-white p-8 transition-all duration-200 ease-out hover:-translate-y-1 hover:shadow-[0_18px_44px_rgba(15,23,42,0.08)]">
|
||||||
|
<div class="inline-flex h-12 w-12 items-center justify-center rounded-2xl bg-amber-50 text-amber-600">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" viewBox="-10 -226 532 468" fill="none">
|
||||||
|
<path
|
||||||
|
d="M32-216c18 0 32 14 32 32v336c0 9 7 16 16 16h400c18 0 32 14 32 32s-14 32-32 32H80c-44 0-80-36-80-80v-336c0-18 14-32 32-32zM144-24c18 0 32 14 32 32v64c0 18-14 32-32 32s-32-14-32-32V8c0-18 14-32 32-32zm144-64V72c0 18-14 32-32 32s-32-14-32-32V-88c0-18 14-32 32-32s32 14 32 32zm80 32c18 0 32 14 32 32v96c0 18-14 32-32 32s-32-14-32-32v-96c0-18 14-32 32-32zm144-96V72c0 18-14 32-32 32s-32-14-32-32v-224c0-18 14-32 32-32s32 14 32 32z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3 class="mt-5 text-2xl font-bold tracking-tight text-slate-900">
|
||||||
|
{{ t('home.features.analytics.title') }}
|
||||||
|
</h3>
|
||||||
|
<p class="mt-3 text-base leading-7 text-slate-600">
|
||||||
|
{{ t('home.features.analytics.description') }}
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-6 rounded-3xl border border-slate-200 bg-white p-6 sm:p-8">
|
||||||
|
<div class="grid gap-6 lg:grid-cols-[1fr_0.9fr] lg:items-center">
|
||||||
|
<div>
|
||||||
|
<p class="text-sm font-semibold uppercase tracking-[0.22em] text-primary">
|
||||||
|
{{ t('home.features.live.title') }}
|
||||||
|
</p>
|
||||||
|
<h3 class="mt-3 text-3xl font-bold tracking-tight text-slate-900">
|
||||||
|
{{ t('home.features.live.title') }}
|
||||||
|
</h3>
|
||||||
|
<p class="mt-4 max-w-2xl text-base leading-8 text-slate-600">
|
||||||
|
{{ t('home.features.live.description') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rounded-[1.75rem] bg-slate-50 p-4">
|
||||||
|
<div class="space-y-3">
|
||||||
|
<div
|
||||||
|
v-for="signal in signalItems"
|
||||||
|
:key="`summary-${signal.label}`"
|
||||||
|
class="flex items-center justify-between rounded-2xl border border-slate-200 bg-white px-4 py-3"
|
||||||
|
>
|
||||||
|
<span class="text-sm text-slate-500">{{ signal.label }}</span>
|
||||||
|
<span class="text-sm font-semibold text-slate-900">{{ signal.value }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ul class="space-y-3 mb-8 text-sm text-slate-600">
|
|
||||||
<li v-for="value in pack.features" :key="value" class="flex items-center gap-3"><Check-Icon
|
|
||||||
class="fas fa-check text-brand-500" /> {{ value }}</li>
|
|
||||||
</ul>
|
|
||||||
<router-link to="/sign-up"
|
|
||||||
:class="cn('btn flex justify-center w-full !py-2.5', pack.tag == 'POPULAR' ? 'btn-primary' : 'btn-outline-primary')">{{
|
|
||||||
pack.buttonText }}</router-link>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</section>
|
|
||||||
|
<section id="pricing" class="bg-white py-20">
|
||||||
|
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="mx-auto mb-14 max-w-3xl text-center">
|
||||||
|
<p class="text-sm font-semibold uppercase tracking-[0.22em] text-primary">
|
||||||
|
{{ pricing.title }}
|
||||||
|
</p>
|
||||||
|
<h2 class="mt-4 text-4xl font-bold tracking-tight text-slate-900 sm:text-5xl">
|
||||||
|
{{ pricing.title }}
|
||||||
|
</h2>
|
||||||
|
<p class="mt-4 text-lg leading-8 text-slate-600">
|
||||||
|
{{ pricing.subtitle }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid gap-6 lg:grid-cols-3 lg:items-stretch">
|
||||||
|
<article
|
||||||
|
v-for="pack in pricing.packs"
|
||||||
|
:key="pack.name"
|
||||||
|
:class="[
|
||||||
|
'relative flex h-full flex-col overflow-hidden rounded-3xl border p-8 shadow-sm transition-all duration-200 ease-out hover:-translate-y-1 hover:shadow-[0_18px_44px_rgba(15,23,42,0.08)]',
|
||||||
|
isFeaturedPack(pack.tag)
|
||||||
|
? 'border-primary bg-primary/[0.04] ring-1 ring-primary/15'
|
||||||
|
: isScalePack(pack.tag)
|
||||||
|
? 'border-slate-200 bg-slate-50/80'
|
||||||
|
: 'border-slate-200 bg-white'
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="pack.tag"
|
||||||
|
:class="[
|
||||||
|
'absolute right-5 top-5 rounded-full px-3 py-1 text-xs font-semibold',
|
||||||
|
isFeaturedPack(pack.tag) ? 'bg-primary text-white' : 'bg-slate-900 text-white'
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
{{ pack.tag }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p class="text-sm font-semibold uppercase tracking-[0.18em] text-slate-500">
|
||||||
|
{{ pack.name }}
|
||||||
|
</p>
|
||||||
|
<div class="mt-5 flex items-end gap-2">
|
||||||
|
<span class="text-5xl font-bold tracking-tight text-slate-900">{{ pack.price }}</span>
|
||||||
|
<span class="pb-2 text-slate-500">{{ t('home.pricing.perMonth') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="mt-8 space-y-4">
|
||||||
|
<li v-for="value in pack.features" :key="value" class="flex items-start gap-3">
|
||||||
|
<span class="mt-0.5 inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-primary/10 text-primary">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||||||
|
<path d="M5 12.5l4.2 4.2L19 7" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span class="text-slate-600">{{ value }}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<RouterLink
|
||||||
|
to="/sign-up"
|
||||||
|
:class="[
|
||||||
|
'mt-10 inline-flex w-full items-center justify-center !rounded-xl !py-3.5',
|
||||||
|
isFeaturedPack(pack.tag) ? 'btn btn-success' : 'btn btn-outline-primary'
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
{{ pack.buttonText }}
|
||||||
|
</RouterLink>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
|
||||||
import { Head } from '@unhead/vue/components'
|
|
||||||
import { cn } from '@/lib/utils';
|
|
||||||
const pricing = {
|
|
||||||
title: "Simple, transparent pricing",
|
|
||||||
subtitle: "Choose the plan that fits your needs. No hidden fees.",
|
|
||||||
packs: [
|
|
||||||
{
|
|
||||||
name: "Hobby",
|
|
||||||
price: "$0",
|
|
||||||
features: [
|
|
||||||
"Unlimited upload",
|
|
||||||
"1 Hour of Storage",
|
|
||||||
"Standard Support",
|
|
||||||
],
|
|
||||||
buttonText: "Start Free",
|
|
||||||
tag: "",
|
|
||||||
bg: "#f9fafb",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Pro",
|
|
||||||
price: "$29",
|
|
||||||
features: [
|
|
||||||
"Ads free player",
|
|
||||||
"Support M3U8",
|
|
||||||
"Unlimited upload",
|
|
||||||
"Custom ads"
|
|
||||||
],
|
|
||||||
buttonText: "Get Started",
|
|
||||||
tag: "POPULAR",
|
|
||||||
bg: "#eff6ff",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Scale",
|
|
||||||
price: "$99",
|
|
||||||
features: [
|
|
||||||
"5 TB Bandwidth",
|
|
||||||
"500 Hours Storage",
|
|
||||||
"Priority Support"
|
|
||||||
],
|
|
||||||
buttonText: "Contact Sales",
|
|
||||||
tag: "Best Value",
|
|
||||||
bg: "#eef4f7",
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -5,21 +5,21 @@
|
|||||||
<div class="flex items-center justify-between h-16">
|
<div class="flex items-center justify-between h-16">
|
||||||
<router-link to="/" class="flex items-center gap-2 cursor-pointer">
|
<router-link to="/" class="flex items-center gap-2 cursor-pointer">
|
||||||
<img class="h-8 w-8" src="/apple-touch-icon.png" alt="Logo" />
|
<img class="h-8 w-8" src="/apple-touch-icon.png" alt="Logo" />
|
||||||
<span class="font-bold text-xl tracking-tight text-slate-900">EcoStream</span>
|
<span class="font-bold text-xl tracking-tight text-slate-900">{{ t('app.name') }}</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
<div class="hidden md:flex items-center space-x-8">
|
<div class="hidden md:flex items-center space-x-8">
|
||||||
<a href="#features"
|
<a href="#features"
|
||||||
class="text-sm font-medium text-slate-600 hover:text-brand-600 transition-colors">Features</a>
|
class="text-sm font-medium text-slate-600 hover:text-brand-600 transition-colors">{{ t('home.nav.features') }}</a>
|
||||||
<a href="#pricing"
|
<a href="#pricing"
|
||||||
class="text-sm font-medium text-slate-600 hover:text-brand-600 transition-colors">Pricing</a>
|
class="text-sm font-medium text-slate-600 hover:text-brand-600 transition-colors">{{ t('home.nav.pricing') }}</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="hidden md:flex items-center gap-4">
|
<div class="hidden md:flex items-center gap-4">
|
||||||
<RouterLink to="/login"
|
<RouterLink to="/login"
|
||||||
class="text-sm font-semibold text-slate-600 hover:text-slate-900 cursor-pointer">Log in
|
class="text-sm font-semibold text-slate-600 hover:text-slate-900 cursor-pointer">{{ t('home.nav.login') }}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<RouterLink to="/sign-up"
|
<RouterLink to="/sign-up"
|
||||||
class="bg-slate-900 hover:bg-black text-white px-5 py-2.5 rounded-lg text-sm font-semibold cursor-pointer">
|
class="bg-slate-900 hover:bg-black text-white px-5 py-2.5 rounded-lg text-sm font-semibold cursor-pointer">
|
||||||
Start for free
|
{{ t('home.nav.startFree') }}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -38,47 +38,49 @@
|
|||||||
<div class="w-6 h-6 bg-brand-600 rounded flex items-center justify-center text-white">
|
<div class="w-6 h-6 bg-brand-600 rounded flex items-center justify-center text-white">
|
||||||
<img class="h-6 w-6" src="/apple-touch-icon.png" alt="Logo" />
|
<img class="h-6 w-6" src="/apple-touch-icon.png" alt="Logo" />
|
||||||
</div>
|
</div>
|
||||||
<span class="font-bold text-lg text-slate-900">EcoStream</span>
|
<span class="font-bold text-lg text-slate-900">{{ t('app.name') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-slate-500 text-sm max-w-xs">Building the video layer of the internet. Designed for
|
<p class="text-slate-500 text-sm max-w-xs">{{ t('home.footer.description') }}</p>
|
||||||
developers.</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4 class="font-semibold text-slate-900 mb-4 text-sm">Product</h4>
|
<h4 class="font-semibold text-slate-900 mb-4 text-sm">{{ t('home.footer.product') }}</h4>
|
||||||
<ul class="space-y-2 text-sm text-slate-500">
|
<ul class="space-y-2 text-sm text-slate-500">
|
||||||
<li><a href="#" class="hover:text-brand-600">Features</a></li>
|
<li><a href="#" class="hover:text-brand-600">{{ t('home.footer.productFeatures') }}</a></li>
|
||||||
<li><a href="#" class="hover:text-brand-600">Pricing</a></li>
|
<li><a href="#" class="hover:text-brand-600">{{ t('home.footer.productPricing') }}</a></li>
|
||||||
<li><a href="#" class="hover:text-brand-600">Showcase</a></li>
|
<li><a href="#" class="hover:text-brand-600">{{ t('home.footer.productShowcase') }}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4 class="font-semibold text-slate-900 mb-4 text-sm">Company</h4>
|
<h4 class="font-semibold text-slate-900 mb-4 text-sm">{{ t('home.footer.company') }}</h4>
|
||||||
<ul class="space-y-2 text-sm text-slate-500">
|
<ul class="space-y-2 text-sm text-slate-500">
|
||||||
<li><a href="#" class="hover:text-brand-600">About</a></li>
|
<li><a href="#" class="hover:text-brand-600">{{ t('home.footer.companyAbout') }}</a></li>
|
||||||
<li><a href="#" class="hover:text-brand-600">Blog</a></li>
|
<li><a href="#" class="hover:text-brand-600">{{ t('home.footer.companyBlog') }}</a></li>
|
||||||
<li><a href="#" class="hover:text-brand-600">Careers</a></li>
|
<li><a href="#" class="hover:text-brand-600">{{ t('home.footer.companyCareers') }}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4 class="font-semibold text-slate-900 mb-4 text-sm">Legal</h4>
|
<h4 class="font-semibold text-slate-900 mb-4 text-sm">{{ t('home.footer.legal') }}</h4>
|
||||||
<ul class="space-y-2 text-sm text-slate-500">
|
<ul class="space-y-2 text-sm text-slate-500">
|
||||||
<li><router-link to="/privacy" class="hover:text-brand-600">Privacy</router-link></li>
|
<li><router-link to="/privacy" class="hover:text-brand-600">{{ t('home.footer.privacy') }}</router-link></li>
|
||||||
<li><router-link to="/terms" class="hover:text-brand-600">Terms</router-link></li>
|
<li><router-link to="/terms" class="hover:text-brand-600">{{ t('home.footer.terms') }}</router-link></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pt-8 border-t border-slate-100 text-center text-sm text-slate-400">
|
<div class="pt-8 border-t border-slate-100 text-center text-sm text-slate-400">
|
||||||
© 2026 EcoStream Inc. All rights reserved.
|
{{ t('home.footer.copyright', { year: 2026 }) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<Head>
|
<Head>
|
||||||
<title>EcoStream - Video infrastructure for modern internet</title>
|
<title>{{ t('home.head.title') }}</title>
|
||||||
<meta name="description"
|
<meta name="description"
|
||||||
content="Seamlessly host, encode, and stream video with our developer-first API. Optimized for speed, built for scale." />
|
:content="t('home.head.description')" />
|
||||||
</Head>
|
</Head>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Head } from '@unhead/vue/components'
|
import { Head } from '@unhead/vue/components'
|
||||||
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,61 +1,101 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="max-w-4xl mx-auto space-y-10" style="opacity: 1; transform: none;">
|
<section class="relative overflow-hidden border-b border-slate-100 bg-gradient-to-b from-slate-50 via-white to-white">
|
||||||
<div class="grow pt-32 pb-12 px-4">
|
<div class="pointer-events-none absolute inset-0">
|
||||||
<div class="max-w-4xl mx-auto space-y-10">
|
<div class="absolute inset-0 opacity-55 bg-[linear-gradient(rgba(148,163,184,0.1)_1px,transparent_1px),linear-gradient(90deg,rgba(148,163,184,0.1)_1px,transparent_1px)] bg-[length:64px_64px] [mask-image:linear-gradient(to_bottom,rgba(0,0,0,0.45),transparent_78%)]"></div>
|
||||||
<div class="space-y-3">
|
<div class="absolute left-0 top-20 h-56 w-56 rounded-full bg-primary/10 blur-3xl"></div>
|
||||||
<p
|
<div class="absolute right-0 top-24 h-64 w-64 rounded-full bg-sky-100 blur-3xl"></div>
|
||||||
class="inline-block px-4 py-1.5 rounded-full bg-info/20 font-bold text-sm uppercase">
|
</div>
|
||||||
{{ pageContent.data.pageSubheading }}</p>
|
|
||||||
<h1 class="text-4xl md:text-5xl font-heading font-extrabold">{{ pageContent.data.pageHeading }}</h1>
|
<div class="relative mx-auto max-w-5xl px-4 pb-16 pt-28 sm:px-6 lg:px-8 lg:pb-20 lg:pt-34">
|
||||||
<p class="text-slate-600 text-lg font-medium">{{ pageContent.data.description }}</p>
|
<div class="mx-auto max-w-4xl">
|
||||||
|
<div class="inline-flex items-center rounded-full border border-primary/15 bg-white/90 px-4 py-2 text-xs font-semibold uppercase tracking-[0.22em] text-primary shadow-sm">
|
||||||
|
{{ pageContent.data.pageSubheading }}
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-white p-8 rounded-xl border border-gray-200 shadow-hard space-y-6">
|
|
||||||
<section v-for="(item, index) in pageContent.data.list" :key="index">
|
<div class="mt-6 max-w-3xl space-y-4">
|
||||||
<h2 class="text-2xl font-bold mb-4">{{ item.heading }}</h2>
|
<h1 class="text-4xl font-bold tracking-tight text-slate-900 sm:text-5xl lg:text-6xl">
|
||||||
<p class="leading-relaxed">{{ item.text }}</p>
|
{{ pageContent.data.pageHeading }}
|
||||||
</section>
|
</h1>
|
||||||
|
<p class="text-lg leading-8 text-slate-600">
|
||||||
|
{{ pageContent.data.description }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-10 rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm sm:p-8 lg:p-10">
|
||||||
|
<div class="space-y-6">
|
||||||
|
<section
|
||||||
|
v-for="(item, index) in pageContent.data.list"
|
||||||
|
:key="index"
|
||||||
|
class="rounded-2xl border border-slate-200 bg-slate-50/70 p-5 transition-all duration-200 ease-out hover:-translate-y-0.5 hover:border-primary/25 hover:bg-white hover:shadow-[0_14px_32px_rgba(15,23,42,0.06)] sm:p-6"
|
||||||
|
>
|
||||||
|
<div class="flex items-start gap-4">
|
||||||
|
<div class="inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-primary/10 text-sm font-bold text-primary">
|
||||||
|
{{ index + 1 }}
|
||||||
|
</div>
|
||||||
|
<div class="min-w-0">
|
||||||
|
<h2 class="text-xl font-bold tracking-tight text-slate-900 sm:text-2xl">
|
||||||
|
{{ item.heading }}
|
||||||
|
</h2>
|
||||||
|
<p class="mt-3 leading-8 text-slate-600">
|
||||||
|
{{ item.text }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {useHead} from "@unhead/vue";
|
import { computed } from 'vue';
|
||||||
const title = "Privacy Policy - Ecostream";
|
import { useTranslation } from 'i18next-vue';
|
||||||
const description = "Read about Ecostream's commitment to protecting your privacy and data security.";
|
import { useHead } from '@unhead/vue';
|
||||||
const pageContent = {
|
|
||||||
head: {
|
const { t } = useTranslation();
|
||||||
title,
|
|
||||||
meta: [
|
const pageContent = computed(() => {
|
||||||
{ name: "description", content: description },
|
const title = t('legal.privacy.title');
|
||||||
{ property: "og:title", content: title },
|
const description = t('legal.privacy.description');
|
||||||
{ property: "og:description", content: description },
|
|
||||||
{ property: "twitter:title", content: title },
|
return {
|
||||||
{ property: "twitter:description", content: description },
|
head: {
|
||||||
{ property: "twitter:image", content: "https://Ecostream.com/thumb.png" }
|
title,
|
||||||
]
|
meta: [
|
||||||
},
|
{ name: 'description', content: description },
|
||||||
data: {
|
{ property: 'og:title', content: title },
|
||||||
pageHeading: "Legal & Privacy Policy",
|
{ property: 'og:description', content: description },
|
||||||
pageSubheading: "Legal & Privacy Policy",
|
{ property: 'twitter:title', content: title },
|
||||||
description: "Our legal and privacy policy.",
|
{ property: 'twitter:description', content: description },
|
||||||
list: [{
|
{ property: 'twitter:image', content: 'https://Ecostream.com/thumb.png' }
|
||||||
heading: "1. Privacy Policy",
|
]
|
||||||
text: "At Ecostream, we take your privacy seriously. This policy describes how we collect, use, and protect your personal information. We only collect information that is necessary for the operation of our service, including email addresses for account creation and payment information for subscription processing."
|
|
||||||
},
|
},
|
||||||
{
|
data: {
|
||||||
heading: "2. Data Collection",
|
pageHeading: t('legal.privacy.pageHeading'),
|
||||||
text: "We collect data such as IP addresses, browser types, and access times to analyze trends and improve our service. Uploaded content is stored securely and is only accessed as required for the delivery of our hosting services."
|
pageSubheading: t('legal.privacy.pageSubheading'),
|
||||||
},
|
description: t('legal.privacy.pageDescription'),
|
||||||
{
|
list: [
|
||||||
heading: "3. Cookie Policy",
|
{
|
||||||
text: "We use cookies to maintain user sessions and preferences. By using our website, you consent to the use of cookies in accordance with this policy."
|
heading: t('legal.privacy.sections.policyTitle'),
|
||||||
},
|
text: t('legal.privacy.sections.policyText')
|
||||||
{
|
},
|
||||||
heading: "4. DMCA & Copyright",
|
{
|
||||||
text: "Ecostream respects the intellectual property rights of others. We respond to notices of alleged copyright infringement in accordance with the Digital Millennium Copyright Act (DMCA). Please report any copyright violations to our support team."
|
heading: t('legal.privacy.sections.dataCollectionTitle'),
|
||||||
}]
|
text: t('legal.privacy.sections.dataCollectionText')
|
||||||
}
|
},
|
||||||
}
|
{
|
||||||
useHead(pageContent.head);
|
heading: t('legal.privacy.sections.cookieTitle'),
|
||||||
|
text: t('legal.privacy.sections.cookieText')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: t('legal.privacy.sections.dmcaTitle'),
|
||||||
|
text: t('legal.privacy.sections.dmcaText')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
useHead(() => pageContent.value.head);
|
||||||
</script>
|
</script>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user