diff --git a/.env.example b/.env.example index 51f7e13..097801e 100644 --- a/.env.example +++ b/.env.example @@ -1,20 +1,9 @@ -# API Configuration +# The Minbar backend (LensBridgeBackend). VITE_API_BASE_URL=http://localhost:8080 -VITE_API_TIMEOUT=10000 -# Application Settings -VITE_APP_NAME=LensBridge -VITE_APP_ENVIRONMENT=development -VITE_APP_VERSION=1.0.0 - -# Upload Configuration -VITE_UPLOAD_MAX_SIZE=10485760 -VITE_SUPPORTED_FORMATS=image/jpeg,image/png,image/webp,video/mp4,video/webm +# tCketManage runs its own console on its own subdomain. The sidebar links out +# to it; nothing in this app talks to it except the board-event ticket picker. +VITE_TCKET_CONSOLE_URL=https://tcket.minbar.ca -# Feature Flags -VITE_ENABLE_DEBUG=true -VITE_ENABLE_ANALYTICS=false - -# Contact Information -VITE_CONTACT_EMAIL=support@lensbridge.tech -VITE_INSTAGRAM_HANDLE=@utmmsa +# Shown in the sidebar footer. Anything other than "production" reads as dev. +VITE_APP_ENVIRONMENT=development diff --git a/NOTES-FOR-BACKEND.md b/NOTES-FOR-BACKEND.md new file mode 100644 index 0000000..e26cca2 --- /dev/null +++ b/NOTES-FOR-BACKEND.md @@ -0,0 +1,147 @@ +# Gaps found while building the Minbar console + +Written against `LensBridgeBackend/openapi.yaml` as of 2026-08-24, while +rebuilding this repo into a pure management console. Everything below is a place +where the console either cannot do something an operator will ask for, or has to +work around the contract to do it. Ordered roughly by how much it hurts. + +None of this blocks the console shipping — each item has a workaround or an +honest "not possible from here" message in the UI. They are worth a look when +you're back. + +--- + +## 1. There is no user search + +`GET /api/admin/users` binds only a `Pageable`. There is no `q`, no `email`, no +`role` filter. The old console *sent* a `search` param and Spring discarded it +server-side, so user search has never worked — it just looked like it did. + +The new People page filters the page in hand and labels the input "Filter this +page", with a line under the table saying other pages are not covered. That is +honest at 34 accounts. It stops being adequate somewhere around 200. + +Cheapest fix: an optional `q` on the existing endpoint matching against +`firstName`, `lastName` and `email`. + +## 2. There is no "fetch one user" + +No `GET /api/admin/users/{userId}`. After a role grant or a permission revoke, +the console has to re-request the whole page it is on and pick the row back out +by id, purely to re-render one modal. See `refresh(userId)` in +`src/pages/People.jsx`. + +Also: the four grant endpoints return a `MessageResponse`, not the updated +`UserInfoResponse`. Returning the user would remove the refetch entirely. + +## 3. Audit filters do not compose + +Five endpoints — `/audit`, `/audit/action/{action}`, `/audit/daterange`, +`/audit/failed`, `/audit/upload/{uploadId}` — and each ignores the others' +criteria. "Failed permission grants in the last week", which is the query you +actually want during an incident, cannot be expressed. + +The console models this honestly: the scope is a single mutually-exclusive +choice and picking one clears the rest, because a row of checkboxes that +silently override each other is worse than a limitation you can see. + +One `GET /api/admin/audit` taking optional `action`, `start`, `end`, +`succeeded`, `adminId` and `targetEntityId` would replace all five. + +Related: `AuditAction` has an `EXPORT_DATA` constant but there is no export +endpoint, so nothing can ever emit it. + +## 4. Audit actions exist for operations that have no endpoint + +`AuditAction` declares `DISABLE_USER`, `ENABLE_USER`, `UNVERIFY_USER`, +`RESET_USER_PASSWORD`, `PROMOTE_USER`, `DEMOTE_USER`, `REMOVE_USER` and +`EXPORT_DATA`. None of them have a corresponding admin endpoint. + +The console can create a user and verify one. It cannot disable a compromised +account, un-verify a mistaken verification, or force a password reset — the +three things you actually want at 2am. Right now the answer is "edit the +database". + +`POST /api/admin/user/{userId}/disable` and `/enable` would be the highest-value +two. + +## 5. Media moderation has no bulk operations + +Approving a batch of forty photos after an event is forty round trips, each one +audited separately. `POST /api/admin/board/uploads/approve` taking a list of +ids would collapse that, and would make the audit log readable afterwards +("approved 40 uploads" instead of forty identical lines). + +## 6. Board content lists are unbounded + +`/api/admin/board/events`, `/posters`, `/socials` and `/weekly-content` all +return bare arrays. Events in particular only ever grow — by the third year +that's a several-hundred-element response on every load of the Content page, and +the console has to filter and sort the whole thing client-side. + +`/uploads` and `/users` already return `Page*`; these should too, or at least +take a date window. + +## 7. `GET /api/tcket/events` documents its Pageable wrong + +The controller is missing `@ParameterObject`, so springdoc writes the `Pageable` +into the spec as a single object-typed query parameter. A generated client +serializes that as `pageable[page]=0&pageable[size]=50`, which Spring's resolver +does not read — it wants flat `page`/`size`/`sort`. + +Already noted in your own `docs/API_CONTRACT.md`. `TicketingService.listEvents` +carries a custom `querySerializer` to flatten it. That workaround can go the +moment the annotation lands. + +## 8. `FrameType` serializes as one thing and is documented as another + +`FrameType` overrides `toString()` to return lowercase snake_case, and that is +what springdoc wrote into the spec (`poster`). Jackson serializes enums with +`name()` unless told otherwise, and nothing tells it otherwise — so the wire +format is `POSTER`. + +`normalizeFrameType` in `src/models/board.js` accepts both, so whichever side +gets fixed the console keeps working. But the contract is currently lying, and +anyone generating a client from it and trusting the enum will get an unlabelled +grey box. `FrameSlot` has no custom `toString()` and is unaffected — which is +why the slot chip resolved while the type did not. + +Either add `@JsonValue` to the lowercase form or drop the `toString()` override. + +## 9. Audience enum case + +`openapi.yaml` documents every `audience` as lowercase (`brothers`, `sisters`, +`both`) and the enum now serializes lowercase while still reading either case. +The console sends lowercase everywhere, including +`IssueEnrollmentTokenRequest` — the old enrollment page sent `BOTH`, which +worked only because of the lenient read. Worth confirming nothing else in the +system depends on the uppercase spelling before the leniency is removed. + +## 10. Small things + +- `GET /api/user/stats` does not exist, but the old Profile page called it on + every load and swallowed the failure. Removed here; mentioning it in case + another client still does. +- `POST /api/admin/user/create` takes `@RequestBody` without `@Valid`, so email + format and field lengths are unchecked server-side. The console mirrors the + column caps client-side, which is not the same as enforcing them. +- The four grant/revoke endpoints take a bare JSON string as the body + (`"BOARD_EDITOR"`). It works, but it types badly in every generated client — + `openapi-typescript` renders it as a literal union that no `string` satisfies, + so `IamService` carries two casts. A one-field wrapper object would be + friendlier. + +--- + +## What the console deliberately does not do + +For the avoidance of doubt, since these are absences by choice rather than gaps: + +- **No ticketing screens.** tCketManage has its own console on its own + subdomain. The only thing Minbar owns is the board-event to ticket-event + *link*, which needs `GET /api/tcket/events` to populate a picker and nothing + else. The sidebar links out. +- **No public surface.** No gallery, no submission form, no signup. Accounts are + created by an administrator through `POST /api/admin/user/create`. If you want + self-service signup back, `POST /api/auth/signup` is still live server-side — + nothing in this app calls it. diff --git a/README.md b/README.md index 7322969..c5381c9 100644 --- a/README.md +++ b/README.md @@ -1,156 +1,135 @@ -# LensBridge Frontend +# Minbar Management Console -A React frontend application for LensBridge - a media upload platform designed for UTM MSA students to share event photos and videos for potential featuring on social media. +The admin console for everything Minbar runs: MusallahBoard content, the display +fleet, media submissions, and who is allowed to touch any of it. -> See Also: [LensBridge Backend](https://github.com/IbraTech/LensBridgeBackend) - The backend API for this application. +This is a **management console and nothing else**. There is no gallery, no +upload form, and no signup — accounts are created by an administrator. The +public-facing app this repository used to be (LensBridge) is gone; what survived +is the moderation queue for the media people still submit. -## Features +Ticketing is not here either. tCketManage has its own console on its own +subdomain; the only thing this app owns is the link between a board event and a +ticketed event, which is one picker inside the events editor. -- 📸 **Media Upload**: Drag-and-drop or click to upload photos and videos -- 🖼️ **Gallery**: Browse and search through community-submitted media -- 📱 **Mobile Responsive**: Optimized for all device sizes -- 🎨 **Modern UI**: Clean, intuitive interface with MSA branding -- ✅ **Form Validation**: Comprehensive form validation and error handling -- 🔒 **Consent Management**: User consent for media usage - -## Tech Stack - -- **React 18** - Modern React with hooks -- **Vite** - Fast build tool and development server -- **Tailwind CSS** - Utility-first CSS framework -- **React Router** - Client-side routing -- **Lucide React** - Beautiful icons - -## Getting Started - -### Prerequisites - -- Node.js 18+ -- npm or yarn - -### Installation - -1. Clone the repository - -```bash -git clone https://github.com/Ibratech04/LensBridgeFrontend.git -cd LensBridgeFrontend -``` - -2. Install dependencies +## Running it ```bash npm install +cp .env.example .env # point VITE_API_BASE_URL at your backend +npm run dev # http://localhost:5175 ``` -3. Start the development server +The backend is [LensBridgeBackend](https://github.com/LensBridge/LensBridgeBackend), +checked out beside this repo. ```bash -npm run dev +npm run build # typecheck, then vite build +npm run lint +npm test # token-refresh + model unit tests +npm run api:generate # regenerate src/api/schema.d.ts from the backend spec +npm run api:check # fail if the committed client is stale ``` -4. Open your browser to the correct URL - -## Available Scripts - -- `npm run dev` - Start development server -- `npm run build` - Build for production -- `npm run preview` - Preview production build -- `npm run lint` - Run ESLint - -## Project Structure +## Layout ``` src/ -├── components/ # Reusable UI components -│ └── Header.jsx # Navigation header -├── pages/ # Page components -│ ├── Home.jsx # Landing page -│ ├── Upload.jsx # Media upload page -│ └── Gallery.jsx # Media gallery page -├── App.jsx # Main app component -├── main.jsx # Entry point -└── index.css # Global styles + api/ generated OpenAPI client + token storage. Never edit schema.d.ts. + services/ one class per backend area; the only place fetch shapes live + models/ backend contract constants (frame types, audiences, durations) + utils/ permissions catalog, authorization helpers, formatting + hooks/ device list / device detail / command stream (STOMP + polling) + components/ + ui/ the design system — Button, Panel, DataTable, Modal, Toast, … + shell/ the authenticated frame: sidebar, top bar, nav model + brand/ the Minbar mark + board/ content editors (events, posters, socials, weekly, slideshow) + devices/ telemetry, command launcher, enrollment + admin/ access editor, user creation, audit display maps + pages/ one file per route ``` -## Key Features - -### Media Upload - -- Drag-and-drop interface for easy file selection -- Support for images (JPG, PNG, GIF) and videos (MP4, MOV, AVI) -- File size limit: 100MB per file -- Real-time file preview -- Form validation with user consent - -### Gallery - -- Grid layout for media display -- Search and filter functionality -- Featured content highlighting -- Media statistics - -### Responsive Design - -- Mobile-first approach -- Optimized for tablets and desktops -- Touch-friendly interactions - -## Environment Variables - -The application uses OS environment variables to configure the API endpoints. - -### Setting Environment Variables - -#### Windows (PowerShell) - -```powershell -# Set environment variable for current session -$env:VITE_API_BASE_URL = "http://localhost:8080" - -# Set environment variable permanently (requires restart) -[System.Environment]::SetEnvironmentVariable("VITE_API_BASE_URL", "http://localhost:8080", [System.EnvironmentVariableTarget]::User) -``` - -#### Windows (Command Prompt) - -```cmd -# Set environment variable for current session -set VITE_API_BASE_URL=http://localhost:8080 - -# Set environment variable permanently -setx VITE_API_BASE_URL "http://localhost:8080" -``` - -#### Linux/macOS - -```bash -# Set environment variable for current session -export VITE_API_BASE_URL=http://localhost:8080 - -# Add to ~/.bashrc or ~/.zshrc for permanent setting -echo 'export VITE_API_BASE_URL=http://localhost:8080' >> ~/.bashrc -``` - -### Available Environment Variables - -- `VITE_API_BASE_URL` - Base URL for the backend API (default: `http://localhost:8080`) - -### Examples - -```bash -# Development -VITE_API_BASE_URL=http://localhost:8080 - -# Production -VITE_API_BASE_URL=https://your-production-api.com - -# Staging -VITE_API_BASE_URL=https://staging-api.your-domain.com -``` - -**Note**: Vite requires environment variables to be prefixed with `VITE_` to be accessible in the browser. - -## License - -This project is licensed under the MIT License. +Two frames and nothing else: `AuthLayout` for the four screens you can reach +signed out, `ConsoleLayout` for everything else. + +## Authorization + +The server checks **permissions**, never role names — every `@PreAuthorize` is +`hasAuthority('domain:resource:verb')`. The console mirrors that exactly: + +- `src/utils/permissions.js` is the catalog, mirrored from the backend enum. +- `` hides a control the user cannot use. +- `` gates a route, and names the missing permission + on the denial screen so people know what to ask for. +- `src/components/shell/nav.js` decides which sidebar entries exist. + +Hiding a control is presentation, not security. The route gates repeat the nav's +permission lists on purpose — a bookmark reaches the route without touching the +sidebar. + +Roles are named bundles and are display-only in this app. Gate on the permission +a control actually needs, so a BOARD_EDITOR gets the poster editor without also +being handed the reboot button. + +## Design + +The palette, type and mark are the **Ember** scheme from the Minbar brand project +(Claude Design `019e31dd`, `Minbar Logo Final.html` and `assets/README.txt`). + +- **Ember (`#F26430`) is the only accent.** One per screen, on the primary + action. A selected filter gets the tinted version (`bg-ember-dim`), an active + nav item gets a tinted lozenge, and status uses its own narrow ramp. If you + find yourself reaching for a second ember button, one of them is a + `secondary`. +- Syne for display type and the wordmark, Montserrat for UI, IBM Plex Mono for + the eyebrow labels and any data you might read back to someone. + +### Two themes + +The console ships light and dark, and the ramp is written so that adding the +second one meant redefining colour and nothing else. + +- **Light** is the default and takes its structure from tCketManage: a white + nav column, a 54px title bar, cards on a light neutral ground. Its surfaces + are deliberately neutral — a warm tint at 96% lightness turns muddy and + fights ember. +- **Dark** keeps those surfaces neutral and goes to near-black (`#0C0C0E`). + The brand sheet does have a dark-first scheme built on the warm `#1C1210`, + and it is what the kiosk runs on — but a hue that reads as "warm black" + behind a photograph reads as orange across a full console page. Ember stays + the only thing on screen carrying chroma. + +Token names are semantic, not literal, which is what lets both themes share +them: `ground` is the app background and `raised` is one step toward the viewer +in either. Light values live in `@theme` in `src/index.css`; dark values are a +`.dark` block that redefines the same names. **Type, radii, motion and the shell +metrics are not part of a theme** — a dark mode that also moves things is two +changes wearing one name. + +Two things in that file are worth knowing before you edit it: + +- The `dark` variant is repointed at a class (`@custom-variant`), not + `prefers-color-scheme`, because "System" has to be a choice among three + rather than the absence of a choice. `next-themes` owns the class, a blocking + script in `index.html` applies it before first paint, and the storage key is + spelled in both places. +- `--shadow-*` values reference `--sh-*` variables instead of literal rgba. + Tailwind compiles a resolved colour into the shadow utility, so a literal + would not follow the theme; a nested `var()` survives compilation and does. + +A handful of values must **not** flip — the app-icon shell is near-black +artwork, and a QR code needs a light field under it whichever way the console +is running. Those use the `--color-deep` / `--color-cream` brand constants. + +Tokens live in `@theme` in `src/index.css`. The mark's geometry — a 7.5-unit +stroke, r24 arc across a 26–74 span, detached platform bar at y84 — is in +`src/components/brand/MinbarMark.jsx`, which also enforces the two brand rules +worth enforcing in code: stroke thickens as the mark shrinks, and below 24px it +switches to the rounded icon shell. + +## Backend gaps + +See [NOTES-FOR-BACKEND.md](./NOTES-FOR-BACKEND.md) — the places where the +console cannot do something an operator will ask for, and the contract +workarounds it currently carries. diff --git a/components.json b/components.json new file mode 100644 index 0000000..aae471b --- /dev/null +++ b/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": false, + "tailwind": { + "config": "", + "css": "src/index.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/shadcn", + "lib": "@/lib", + "hooks": "@/hooks" + } +} diff --git a/docs/ADMIN_API.md b/docs/ADMIN_API.md deleted file mode 100644 index 048812f..0000000 --- a/docs/ADMIN_API.md +++ /dev/null @@ -1,157 +0,0 @@ -# Admin API - LensBridge Frontend - -This document describes the recommended admin HTTP API endpoints used by the `BoardManagement` UI. All admin endpoints require authentication and the `ROLE_ROOT` permission unless noted otherwise. - -## Auth -- POST /api/admin/login - - Body: { email, password } - - Response: { token, user: { id, name, roles } } - -- GET /api/admin/me - - Headers: `Authorization: Bearer ` - - Response: { id, name, email, roles } - -## Error / Validation format -- 4xx/5xx responses should follow: - - { error: 'Short message', details?: { field?: 'message' } } - -## Boards -- GET /api/admin/boards - - Query: none - - Response: [{ id, name, location, boardConfig, jummahSchedules, events, posters, frames, dailyContent }] - -- GET /api/admin/boards/:boardId - - Response: full board payload (same shape as used in UI) - -- POST /api/admin/boards - - Body: { name, location, boardConfig } - - Response: created board object - -- PUT /api/admin/boards/:boardId - - Body: partial/full board fields to update - - Response: updated board - -- DELETE /api/admin/boards/:boardId - - Response: { success: true } - -## Board Config (settings) -- GET /api/admin/boards/:boardId/config - - Response: boardConfig object - -- PUT /api/admin/boards/:boardId/config - - Body: boardConfig (e.g., timezone, frameDuration, darkModeAfterIsha, enableScrollingMessage, scrollingMessage, etc.) - - Response: updated boardConfig - -Notes: The UI expects `darkModeAfterIsha` as a boolean and other fields matching `board_models.js`. - -## Jummah Schedules -(Jummah is modeled as `jummahSchedules: [{ id, date, isRecurring, prayers } ]` at board-level.) - -- GET /api/admin/boards/:boardId/jummah-schedules - - Response: array of schedules - -- POST /api/admin/boards/:boardId/jummah-schedules - - Body: { date (YYYY-MM-DD), isRecurring (bool), prayers: [{ time, khatib, location }] } - - Response: created schedule - -- PUT /api/admin/boards/:boardId/jummah-schedules/:scheduleId - - Body: updated schedule - - Response: updated schedule - -- DELETE /api/admin/boards/:boardId/jummah-schedules/:scheduleId - - Response: { success: true } - -- POST /api/admin/boards/:boardId/jummah-schedules/:scheduleId/duplicate - - Body: { targetDate (YYYY-MM-DD) } or server can auto-pick next available Friday - - Response: duplicated schedule - -Validation notes: -- `date` should be ISO date string (YYYY-MM-DD) -- `prayers` must be non-empty and max 5 items -- Audience is not required (prayers broadcast to both musallahs) - -## Events -- GET /api/admin/boards/:boardId/events - - Response: array of events - -- POST /api/admin/boards/:boardId/events - - Body: { name, startTimestamp, endTimestamp, location, description, allDay (bool), audience (optional: 'brothers'|'sisters'|'both') } - - Response: created event - -- PUT /api/admin/boards/:boardId/events/:eventId - - Body: updated event - - Response: updated event - -- DELETE /api/admin/boards/:boardId/events/:eventId - - Response: { success: true } - -- PATCH /api/admin/boards/:boardId/events/order - - Body: { order: [eventId, ...] } - - Response: { success: true } - -## Posters (slides) -- GET /api/admin/boards/:boardId/posters - - Response: array of posters - -- POST /api/admin/uploads - - Purpose: return signed URL or direct-upload token for storing poster images/videos - - Body: { filename, contentType, size } - - Response: { uploadUrl, assetUrl, uploadId } - - Note: UI uses returned `uploadUrl` to PUT the file, then calls create poster with `assetUrl`. - -- POST /api/admin/boards/:boardId/posters - - Body: { title, imageUrl (assetUrl), duration, startDate, endDate, audience (optional) } - - Response: created poster - -- PUT /api/admin/boards/:boardId/posters/:posterId - - Body: updated poster - - Response: updated poster - -- DELETE /api/admin/boards/:boardId/posters/:posterId - - Response: { success: true } - -- PATCH /api/admin/boards/:boardId/posters/order - - Body: { order: [posterId, ...] } - - Response: { success: true } - -Implementation note: Support both direct binary upload to cloud (signed URL) and server-side proxy if needed. - -## Frames (slideshow / frame definitions) -- GET /api/admin/boards/:boardId/frames - - Response: array of frame definitions - -- POST /api/admin/boards/:boardId/frames - - Body: { id, title, posterIds, layoutSettings } - - Response: created frame - -- PUT /api/admin/boards/:boardId/frames/:frameId - - Body: updated frame - - Response: updated frame - -- DELETE /api/admin/boards/:boardId/frames/:frameId - - Response: { success: true } - -## Daily Content -- GET /api/admin/boards/:boardId/daily-content - - Response: [{ date, frameDefinitions, activePosters }] - -- PUT /api/admin/boards/:boardId/daily-content/:date - - Body: { frameDefinitions, activePosters } - - Response: updated day content - -## Miscellaneous -- GET /api/admin/boards/:boardId/stats - - Response: summary counts (frames, posters, events, jummahSchedules, upcomingToday) - -- POST /api/admin/boards/:boardId/publish - - Body: { target: 'now'|'schedule', scheduleTime? } - - Response: { success: true } - -## Permissions & Security -- All admin endpoints should validate the `Authorization` header and ensure the user has `ROLE_ROOT`. -- Rate-limit upload endpoints and validate filetypes/sizes. -- Validate date/time fields server-side using timezone-aware logic. - ---- - -If you want, I can also generate a compact OpenAPI schema (YAML/JSON) from this spec to use for client generation. \ No newline at end of file diff --git a/docs/GALLERY_CACHE.md b/docs/GALLERY_CACHE.md deleted file mode 100644 index e69de29..0000000 diff --git a/eslint.config.js b/eslint.config.js index 415b48a..d9c61a6 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -5,7 +5,8 @@ import reactRefresh from 'eslint-plugin-react-refresh' import { defineConfig, globalIgnores } from 'eslint/config' export default defineConfig([ - globalIgnores(['dist']), + globalIgnores(['dist', 'dev-dist']), + { files: ['**/*.{js,jsx}'], extends: [ @@ -29,4 +30,13 @@ export default defineConfig([ ], }, }, + + // Vendored shadcn/ui source. It is copied in by the CLI, not written here, and + // it exports its cva variant maps alongside its components -- which the + // fast-refresh rule objects to and which is not ours to restructure. Lint it + // for real problems, not for a convention it was never written against. + { + files: ['src/components/shadcn/**/*.{js,jsx}'], + rules: { 'react-refresh/only-export-components': 'off' }, + }, ]) diff --git a/index.html b/index.html index 0b6bf4e..dbbf27b 100644 --- a/index.html +++ b/index.html @@ -2,22 +2,69 @@ - + - - - - - - - - LensBridge + + + + + + + + + + + + + + + + + Minbar Console @@ -104,14 +135,15 @@ diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..0486e3b --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, + "exclude": ["node_modules", "dist"] +} diff --git a/package-lock.json b/package-lock.json index 8ba11e2..22ee604 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,21 +1,27 @@ { - "name": "lensbridgefrontend", + "name": "minbar-console", "version": "0.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "lensbridgefrontend", + "name": "minbar-console", "version": "0.0.0", "dependencies": { "@stomp/stompjs": "^7.3.0", - "heic2any": "^0.0.4", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", "lucide-react": "^0.525.0", + "next-themes": "^0.4.6", "openapi-fetch": "^0.17.0", "qrcode.react": "^4.2.0", + "radix-ui": "^1.6.7", "react": "^19.1.0", "react-dom": "^19.1.0", - "react-router-dom": "^7.6.3" + "react-router-dom": "^7.6.3", + "sonner": "^2.0.8", + "tailwind-merge": "^3.6.0", + "tw-animate-css": "^1.4.0" }, "devDependencies": { "@eslint/js": "^9.29.0", @@ -954,6 +960,44 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz", + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.8.0", + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.9.tgz", + "integrity": "sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.8.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", + "license": "MIT" + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -992,97 +1036,1593 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", + "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@radix-ui/number": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.3.tgz", + "integrity": "sha512-Road2bidD0uu/1BGDOWNdPI06g0lIRy6IF9GZcIrDK2KGItfor8IQwQa+yM2ERgHM1MmHxaxpTzk0/Jp42lNfA==", + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.7.tgz", + "integrity": "sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-accessible-icon": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accessible-icon/-/react-accessible-icon-1.1.15.tgz", + "integrity": "sha512-WTQwcAvQf5sOcuUyi90lKPbhwcvQ+j55cjrSmeaN+L2vKU3DooOvlKw2MDeiJ5IkV5N905KW0/fGojKOBhD11A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-visually-hidden": "1.2.11" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-accordion": { + "version": "1.2.20", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.20.tgz", + "integrity": "sha512-jDhG9FvAEnlhnjrsINbNXcUa4G+L1KqSkJSunkbKEzFRcAb52jvM0PjPxPRvhe1HNc5F5yc0yzzWeeqlH4yBIg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collapsible": "1.1.20", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-alert-dialog": { + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.23.tgz", + "integrity": "sha512-VAYOiQRqj3GPpYJE0I9J+X8Ip05cyVlNdKOFeiGS2Ou1HHGfpl0BxOyZm6nmVDyU+W+NF3/XLzmjHmVGydhwgA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dialog": "1.1.23", + "@radix-ui/react-primitive": "2.1.10" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.15.tgz", + "integrity": "sha512-v4zggRcjadnI+ClKDuijlQEW4tw3NoaeHc/PwpKnLoLLKNUG4InLegkstooLcRIUWCs+8L22dGURCVuFfOKfnA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-aspect-ratio": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.15.tgz", + "integrity": "sha512-fy+dyVR+90nelK8rqIznFlxzx7uPcGbhxH8Nfr2bHb4UfSe+e3hklOC0luK0hDwVwnRX7xTRySpsrQVeW+/oNQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-avatar": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.2.6.tgz", + "integrity": "sha512-4ULOTJ/mqy2hT9GlWa/MFHxHSvH3nJzHnZM1waNsc5Bonv7i70aNenghXmD97S6OJ81ekXONGGt4nT1r0PfEdA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-is-hydrated": "0.1.3", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.11.tgz", + "integrity": "sha512-Gnptr9pDDQxD3hgq2dtPbtrp/c2qH1mBwIzw3X/ivrMb2e1t0jMTi606fVEqFPaQR1ggXIVQWKj3P2WW9v7zGQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-size": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collapsible": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.20.tgz", + "integrity": "sha512-mcGesGplBnzN2sbvJETzpCNfSMyPnb29q1GRLU+Ib7bJrpIG2ywmRoh2V5VbA2uNvKikKUlVbAPks7JDjz4A8Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.15.tgz", + "integrity": "sha512-9W+B9NPF0NaaPh/1NJd3+KqsnlLqU9H7T2rvww+fp+T/evVXdNAyYcnfRQZFOjkR1ajQp3yORlqnI8soawLvNA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.5.tgz", + "integrity": "sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.2.tgz", + "integrity": "sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context-menu": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.3.7.tgz", + "integrity": "sha512-CtXP35dxaB5T3zXSd+E3uHe/QpXcpYnZmxp6OaIbfthtfW4wyb77M23BG+bwIJDtsMwEP/YssdsmNyZu7jhWew==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-menu": "2.1.24", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.23.tgz", + "integrity": "sha512-Ksw4WeROkO4rC9k/onilX/Ao2Cr1ku1unMNH+XSCcP4jSXYu7HDsg9n4ojMjVb22XpYjAQ9qfrFlVbru1vXDUA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.4.tgz", + "integrity": "sha512-5pzg4FGQNpExhnhT2zlrP1wZFaYCd1K0nYWoFAdcYoYK868IEigqMX3B3f8yIoRlAhAeDWciLI6ZdCKHF9P4Vg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.19.tgz", + "integrity": "sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-effect-event": "0.0.5" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.24.tgz", + "integrity": "sha512-geq8l2rJkxvkXsT9RMgtUE3P8pITFpTsvYpbySi1IH4fZEABD/Gp85myayFgxk0ktljGMJnCbeFkyTusvSvv7g==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-menu": "2.1.24", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.6.tgz", + "integrity": "sha512-RNOJjfZMTyBM6xYmV3IVGXkPjIhcBAuv48POevAXwrGJhkWZ9p1rFoIS1JFooPuT193AZmRsCPhpoVJxx6OPoQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.16.tgz", + "integrity": "sha512-wmRZ2WWLvmt6KHy2rNPOdPUjwq5xOHY02+m+udwJTn0aNIox/rkskAvJTyTLGhPK6KgrUjlJUJpgmx/+wFiFIQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-form": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-form/-/react-form-0.1.16.tgz", + "integrity": "sha512-Q4TLEn2A7TAypxwmd6R9EwrlXDvkfYSDMrq9/887AXAGh+G1rH+kYJKSTv+Si9Y0JPKTwKYv6PviAJosysNimA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-label": "2.1.15", + "@radix-ui/react-primitive": "2.1.10" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-hover-card": { + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.23.tgz", + "integrity": "sha512-H8qONfZd3ltrU3+jHCIgITbWo6e1iTKvP9DHdrvYbX48ooRM5FjEDTn16AMwdfuOGkWdZEhpl3PLL/Wk/AnHDQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.4.tgz", + "integrity": "sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.15.tgz", + "integrity": "sha512-o/rdYEwZTTo5tjknnPeyQFU45kUC4i/XyeDPP+HGyi6XqpOP6Zf5Ya5vh/Yfe9Id5JiuWnnAx2XqIeD3UYZt0g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.24.tgz", + "integrity": "sha512-uW7RVuU6Lp/ZtfeY4b3kL32zccgEWvPv1+cf17ubYzHa9cL8AHokmk36cG/XEiH/smbQvumnieXX9j/e9RqJWA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-callback-ref": "1.1.4", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menubar": { + "version": "1.1.24", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.24.tgz", + "integrity": "sha512-eeVs0vf7cuqXaM0qLQCPcufImiJNVBXdJDLu7ZGYl2732UH23Qat/foNGrr6vYV3/DdTsBqASoggUFgH14OcZA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-menu": "2.1.24", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-use-controllable-state": "1.2.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-navigation-menu": { + "version": "1.2.22", + "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.22.tgz", + "integrity": "sha512-ou7iLEJ+yrhQndkkA4U21XIdS/CS45F4iXIkTZcb6/Ne9EMsOuDudVmCwmDnfFZZ+y1FZqXRNSIgBy+YMvZVZg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-previous": "1.1.4", + "@radix-ui/react-visually-hidden": "1.2.11" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-one-time-password-field": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-one-time-password-field/-/react-one-time-password-field-0.1.16.tgz", + "integrity": "sha512-Tj9P6ntAJEw52oq/F0AGknXR4XncxEt7XU47O3xJQOiWfLzEy3d9gtgKfvjSzGxzHkfL+VzvxGu2KTFsloJqXw==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.3", + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-effect-event": "0.0.5", + "@radix-ui/react-use-is-hydrated": "0.1.3", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-password-toggle-field": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-password-toggle-field/-/react-password-toggle-field-0.1.11.tgz", + "integrity": "sha512-4gvFnmDXu3dgj21CqsufzIameRvlRd4SBqaWhcrlrNhRo0Y5i/49AmRJYe1fdAM3G2VNBbmin4b0D6cdQocwgw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-effect-event": "0.0.5", + "@radix-ui/react-use-is-hydrated": "0.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.23.tgz", + "integrity": "sha512-mw58MrBlyHWFisTOYignD0vf/3gdcgAR+9of1s9G/38CbFiUwH1nCDkc0AUM9IrXFgN5Ue8n45j9WCgyM1sbiQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-controllable-state": "1.2.6", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.3.7.tgz", + "integrity": "sha512-UsJrrd7w4wuKKTdvd/DNERVlwSlUcyXzjhyDwBk+3aPOsCjOY6ZSbxuw8E6lZTjjfP8Cpd0J8VVkrYUWyGYXyg==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-rect": "1.1.4", + "@radix-ui/react-use-size": "1.1.4", + "@radix-ui/rect": "1.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.17.tgz", + "integrity": "sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.10.tgz", + "integrity": "sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.10.tgz", + "integrity": "sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.3.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.16.tgz", + "integrity": "sha512-5XnomAsoZZCY+KNTxbIghpGqPruZvKFNlvcAljVAOdDRDsH4/OZQxhtwo5wdtoDM5R6MhJBb2sPnDuRFep3lzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.4.7.tgz", + "integrity": "sha512-cgYFEkntCxppHZgtSZ+7vh0wbZQ+IC7PPMw8DSnRG27B6kDd32/Zw0OJt7dGDigCoprMuWHjg2PvUn3PYvPFoQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-size": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.19.tgz", + "integrity": "sha512-V9jI6hDjT7l3jsCQD9bLNvDLM3tH/gdbOTp7Tefp3hbbgCGQoK7tUvrWiRlcoBHIZ809ElXwNQwVo0B98LuTXQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-is-hydrated": "0.1.3", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-scroll-area": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.18.tgz", + "integrity": "sha512-Zn5Cd171wxsO3Dfg8HaW6RifTb9CYTKQJHs/G4+LN1GfmJpaQMZQyQxMprVPHpaz7QY4l9BxK2JwQuzHsXC8nA==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.3", + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.3.7.tgz", + "integrity": "sha512-WFGImkmbzcfxeIwq/+4HvRN0pizBwbwQUED4I13ezQsDdfl38ZntN6TmR8XaSzPBqoCToe8rF75j6NPNDSzhbg==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.3", + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-previous": "1.1.4", + "@radix-ui/react-visually-hidden": "1.2.11", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.15.tgz", + "integrity": "sha512-jOLO4lssEzWpoDu7G+Ze4VjwMRUBt291pnZD0gmalREZipnTX3wadQo7Fy48GCTfe14/YRN6rw/rOJqrE85Wxw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slider": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.4.7.tgz", + "integrity": "sha512-mTSLf1GC/C0moWjTbvCM6Qn/gBjvlFt1azuWF2v7MN5C3Zq2U2J2lN3ZEYkpujuOU5Ro7A28wkviSxaKnG0BYg==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.3", + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-previous": "1.1.4", + "@radix-ui/react-use-size": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.3.tgz", + "integrity": "sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.5" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.3.7.tgz", + "integrity": "sha512-48tB/4dn2UVLBCYhTu9AuR63IHl73l/qLbLgxd86noTUor4/K4LFDAcYjK+isP5313qxaFpjPVogE7+Y0/V3Kw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-size": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.21.tgz", + "integrity": "sha512-UKxJlZid7FVtsk/WTxj4i4uSEgj2Au+KBbS7SQyTlzMhhn+86Cz3tISZdTa87bfEfcuvZezf2ZsxD4xuEKtkog==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-use-controllable-state": "1.2.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast": { + "version": "1.2.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.23.tgz", + "integrity": "sha512-ofhyAsYaocRGOs/n0XWdUOSVzEAG6BfrMVM8z0c0kLEWY38w/0WuMFPTJP/HVaZPYkMvHZoKIIhNcjbTCBILPg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-visually-hidden": "1.2.11" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.18.tgz", + "integrity": "sha512-7lonPlKfSacd20GlOBx2ltuVKz9oqWYZz+oMQyOltw6t1y2nyftj2ZmwwUHYn49kqfDWcp8dNZm5NgV+5Z+mug==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle-group": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.19.tgz", + "integrity": "sha512-OtnwuSVjd1Ofi+AdnvhsjQdyuhCDwYs1w9RyB5BN/OavXOVQo42SYqQjwUnbPnaiPFBpQ9aX70dWeee+v2oBLA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-toggle": "1.1.18", + "@radix-ui/react-use-controllable-state": "1.2.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toolbar": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.1.19.tgz", + "integrity": "sha512-Ph0IvtYw4VB12ZnZg+YtrGs8yJQsnizwo/zu0R4Y/nWugtJzA7Pg1eWeuDR9+LSqn+xjamss+UOSOJJJ4gx8jw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-separator": "1.1.15", + "@radix-ui/react-toggle-group": "1.1.19" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.16.tgz", + "integrity": "sha512-6EamKFRRnlpdadndbZ6LMwycfwkwPte1B42hs6QA0gYhjaOKqW4PZ4pjaW9UrlDX5eVt/OjncE7BFTPL5nmZhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-visually-hidden": "1.2.11" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.4.tgz", + "integrity": "sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.6.tgz", + "integrity": "sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-use-effect-event": "0.0.5", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.5.tgz", + "integrity": "sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.5.tgz", + "integrity": "sha512-ge3ipobwSXTj4JyVtswQ7qZj0ZHdtbGuOno/LrgAAeSxtsJ6Vs4Gz5IkPH2bmqpjcLUFoqGhA/mueuIf63UXlA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.4" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" + "node_modules/@radix-ui/react-use-is-hydrated": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.3.tgz", + "integrity": "sha512-umO/aJ+82CpOnhDZUTbILCQf7kU/g0iv+oGs/Q8jw7IkhWBzaEP4sA268PhFAJTFetbwp3ICc6ktpI4TqtxcIw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.4.tgz", + "integrity": "sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, - "engines": { - "node": ">=18.0.0" + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", - "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", - "dev": true, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.4.tgz", + "integrity": "sha512-XoSLhbRbqxFtgJoi2fNHA3C6pDlY34x508vUpUGoFZfvePfHXHbE1lC4FYFMnJWgiCRroSTw6fOsXQoVS9RwZg==", "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.4.tgz", + "integrity": "sha512-cSOCh6JlkmfjLyNcLiu2nB4v+nm+dkZ+Q5KHWk/soo4U7ZLiEQFKHK9/YmtBHjfCEaU43IBKQOc4/uJmCaiCTQ==", "license": "MIT", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" + "@radix-ui/rect": "1.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.4.tgz", + "integrity": "sha512-D3anSY15EJoxrihpsXI6SMrmmonnQtR2ni7arO+Lfdg3O95b9hNXxONk8jA5C8ANdF/h5HMAxejgs8PWJ6rlhw==", "license": "MIT", - "engines": { - "node": ">=6.0.0" + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", - "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.29", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", - "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", - "dev": true, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.11.tgz", + "integrity": "sha512-NFS86RYYZb4/exihaESBGOpMJFz8MGLAfu3mOBSGByVnVPC9JPASfYubxd/8KbkQK0sYAv8lVQDEQukDX/qXvQ==", "license": "MIT", "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "@radix-ui/react-primitive": "2.1.10" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, + "node_modules/@radix-ui/rect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.3.tgz", + "integrity": "sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==", + "license": "MIT" + }, "node_modules/@redocly/ajv": { "version": "8.11.2", "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.2.tgz", @@ -1884,7 +3424,7 @@ "version": "19.1.8", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz", "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", - "dev": true, + "devOptional": true, "license": "MIT", "peer": true, "dependencies": { @@ -1895,8 +3435,9 @@ "version": "19.1.6", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.6.tgz", "integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==", - "dev": true, + "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.0.0" } @@ -2006,6 +3547,18 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/autoprefixer": { "version": "10.4.21", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", @@ -2161,6 +3714,27 @@ "node": ">=18" } }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2234,7 +3808,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/debug": { @@ -2272,6 +3846,12 @@ "node": ">=8" } }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, "node_modules/electron-to-chromium": { "version": "1.5.179", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.179.tgz", @@ -2666,6 +4246,15 @@ "node": ">=6.9.0" } }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -2709,12 +4298,6 @@ "node": ">=8" } }, - "node_modules/heic2any": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/heic2any/-/heic2any-0.0.4.tgz", - "integrity": "sha512-3lLnZiDELfabVH87htnRolZ2iehX9zwpRyGNz22GKXIu0fznlblf0/ftppXKNqS26dqFSeqfIBhAmAj/uSp0cA==", - "license": "MIT" - }, "node_modules/https-proxy-agent": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", @@ -3290,6 +4873,16 @@ "dev": true, "license": "MIT" }, + "node_modules/next-themes": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", + "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", @@ -3554,6 +5147,83 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/radix-ui": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/radix-ui/-/radix-ui-1.6.7.tgz", + "integrity": "sha512-QBdhh1arIEUvPC0dQ5+nwWAxt7+N+oP/9jPwjJkGFoSk/sqxg32gJtSXGtFh8frAIcS6oC9cx2Q+7KYCQLOAeA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-accessible-icon": "1.1.15", + "@radix-ui/react-accordion": "1.2.20", + "@radix-ui/react-alert-dialog": "1.1.23", + "@radix-ui/react-arrow": "1.1.15", + "@radix-ui/react-aspect-ratio": "1.1.15", + "@radix-ui/react-avatar": "1.2.6", + "@radix-ui/react-checkbox": "1.3.11", + "@radix-ui/react-collapsible": "1.1.20", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-context-menu": "2.3.7", + "@radix-ui/react-dialog": "1.1.23", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-dropdown-menu": "2.1.24", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-form": "0.1.16", + "@radix-ui/react-hover-card": "1.1.23", + "@radix-ui/react-label": "2.1.15", + "@radix-ui/react-menu": "2.1.24", + "@radix-ui/react-menubar": "1.1.24", + "@radix-ui/react-navigation-menu": "1.2.22", + "@radix-ui/react-one-time-password-field": "0.1.16", + "@radix-ui/react-password-toggle-field": "0.1.11", + "@radix-ui/react-popover": "1.1.23", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-progress": "1.1.16", + "@radix-ui/react-radio-group": "1.4.7", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-scroll-area": "1.2.18", + "@radix-ui/react-select": "2.3.7", + "@radix-ui/react-separator": "1.1.15", + "@radix-ui/react-slider": "1.4.7", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-switch": "1.3.7", + "@radix-ui/react-tabs": "1.1.21", + "@radix-ui/react-toast": "1.2.23", + "@radix-ui/react-toggle": "1.1.18", + "@radix-ui/react-toggle-group": "1.1.19", + "@radix-ui/react-toolbar": "1.1.19", + "@radix-ui/react-tooltip": "1.2.16", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-effect-event": "0.0.5", + "@radix-ui/react-use-escape-keydown": "1.1.5", + "@radix-ui/react-use-is-hydrated": "0.1.3", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-size": "1.1.4", + "@radix-ui/react-visually-hidden": "1.2.11" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/react": { "version": "19.1.0", "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", @@ -3587,6 +5257,53 @@ "node": ">=0.10.0" } }, + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-router": { "version": "7.18.2", "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.2.tgz", @@ -3625,6 +5342,28 @@ "react-dom": ">=18" } }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -3735,6 +5474,22 @@ "node": ">=8" } }, + "node_modules/sonner": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.8.tgz", + "integrity": "sha512-UM/ByIoFra8yzV75n1o0Puu0bw5U/9UNnDacrJNspekBewIfsQ3D6ez1nvlWpt7aTsO6rujQtifBpycwIivqlg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -3771,6 +5526,16 @@ "node": ">=8" } }, + "node_modules/tailwind-merge": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz", + "integrity": "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", @@ -3832,6 +5597,21 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tw-animate-css": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz", + "integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Wombosvideo" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -3921,6 +5701,49 @@ "dev": true, "license": "MIT" }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/vite": { "version": "7.3.6", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", diff --git a/package.json b/package.json index e22c4cf..237980e 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "lensbridgefrontend", + "name": "minbar-console", "private": true, "version": "0.0.0", "type": "module", @@ -15,13 +15,19 @@ }, "dependencies": { "@stomp/stompjs": "^7.3.0", - "heic2any": "^0.0.4", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", "lucide-react": "^0.525.0", + "next-themes": "^0.4.6", "openapi-fetch": "^0.17.0", "qrcode.react": "^4.2.0", + "radix-ui": "^1.6.7", "react": "^19.1.0", "react-dom": "^19.1.0", - "react-router-dom": "^7.6.3" + "react-router-dom": "^7.6.3", + "sonner": "^2.0.8", + "tailwind-merge": "^3.6.0", + "tw-animate-css": "^1.4.0" }, "devDependencies": { "@eslint/js": "^9.29.0", diff --git a/public/Bismillah_Calligraphy1.svg b/public/Bismillah_Calligraphy1.svg deleted file mode 100644 index 6f355ad..0000000 --- a/public/Bismillah_Calligraphy1.svg +++ /dev/null @@ -1,164 +0,0 @@ - - - - - - - - - - - - - - diff --git a/public/lensbridge-logo.svg b/public/lensbridge-logo.svg deleted file mode 100644 index e37f2f0..0000000 --- a/public/lensbridge-logo.svg +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - diff --git a/public/minbar-icon.svg b/public/minbar-icon.svg new file mode 100644 index 0000000..e9e28ae --- /dev/null +++ b/public/minbar-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/minbar-mark.svg b/public/minbar-mark.svg new file mode 100644 index 0000000..c7e06df --- /dev/null +++ b/public/minbar-mark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/vite.svg b/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/App.css b/src/App.css deleted file mode 100644 index 1c27689..0000000 --- a/src/App.css +++ /dev/null @@ -1,4 +0,0 @@ -/* Custom styles for LensBridge */ -.app { - min-height: 100vh; -} diff --git a/src/App.jsx b/src/App.jsx index 7c3a584..4e8cca3 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,120 +1,226 @@ -import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; +import { lazy, Suspense } from 'react'; +import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; +import { ThemeProvider } from 'next-themes'; import { AuthProvider } from './context/AuthContext'; -import Header from './components/Header'; -import Footer from './components/Footer'; +import { ToastProvider, TooltipProvider, Spinner } from './components/ui'; +import ConsoleLayout from './components/shell/ConsoleLayout'; import ProtectedRoute from './components/ProtectedRoute'; import PermissionRoute from './components/PermissionRoute'; -import { - PERMISSIONS, - ADMIN_SECTION_PERMISSIONS, - BOARD_SECTION_PERMISSIONS, -} from './utils/permissions'; -import Home from './pages/Home'; -import Upload from './pages/Upload'; -import Gallery from './pages/Gallery'; -import AdminDashboard from './pages/AdminDashboard'; -import BoardManagement from './pages/BoardManagement'; -import DeviceList from './pages/DeviceList'; -import EnrollDevice from './pages/EnrollDevice'; -import DeviceDetail from './pages/DeviceDetail'; +import { PERMISSIONS } from './utils/permissions'; + import Login from './pages/Login'; -import Signup from './pages/Signup'; -import SignupSuccess from './pages/SignupSuccess'; -import ConfirmEmail from './pages/ConfirmEmail'; import ForgotPassword from './pages/ForgotPassword'; import ResetPassword from './pages/ResetPassword'; -import TermsOfService from './pages/TermsOfService'; -import PrivacyPolicy from './pages/PrivacyPolicy'; -import Profile from './pages/Profile'; -import './App.css'; +import ConfirmEmail from './pages/ConfirmEmail'; + +/** + * Console routes load on demand. + * + * Radix added roughly 56KB gzipped to a single bundle that already sat past + * Vite's size warning. Nobody opens all eleven of these in a session -- a + * BOARD_EDITOR may never open any of the access pages -- so the editors and + * their dependencies (the QR library, the STOMP client) should not be in the + * first paint. The four auth screens stay eager: they are what a signed-out + * visitor sees first, and a spinner before the login box is a bad trade. + */ +const Overview = lazy(() => import('./pages/Overview')); +const BoardContent = lazy(() => import('./pages/BoardContent')); +const Displays = lazy(() => import('./pages/Displays')); +const DisplayDetail = lazy(() => import('./pages/DisplayDetail')); +const PrayerSpaces = lazy(() => import('./pages/PrayerSpaces')); +const MediaQueue = lazy(() => import('./pages/MediaQueue')); +const People = lazy(() => import('./pages/People')); +const Roles = lazy(() => import('./pages/Roles')); +const AuditLog = lazy(() => import('./pages/AuditLog')); +const Account = lazy(() => import('./pages/Account')); +const NotFound = lazy(() => import('./pages/NotFound')); -function App() { + +/** + * The Minbar management console. + * + * Two frames and nothing else: `AuthLayout` for the four screens you can reach + * signed out, `ConsoleLayout` for everything else. There is no public surface — + * no gallery, no submission form, no marketing pages. Accounts are created by + * an administrator, which is why `/signup` is gone rather than hidden. + * + * Route gates repeat the permission lists that `components/shell/nav.js` uses + * to build the sidebar. That duplication is deliberate: hiding a link is not + * authorization, and someone with a bookmark reaches the route directly. + * + * Each gate asks for the narrowest permission that makes the page worth + * opening — a read, in every case. The controls inside gate themselves; see + * `utils/permissions.js`. + */ +/** Holds the layout's shape while a route chunk arrives, so nothing jumps. */ +function RouteFallback() { return ( +
+ +
+ ); +} + +/** + * Where the chosen theme is remembered. + * + * `index.html` reads this same key in a blocking script before first paint, so + * the two must not drift — a rename here without one there costs you a white + * flash on every load in dark mode, and nothing else, which is exactly the kind + * of bug that ships. + */ +const THEME_STORAGE_KEY = 'minbar-theme'; + +export default function App() { + return ( + /* + `attribute="class"` to match the `dark` variant in index.css, and + `disableTransitionOnChange` because roughly every surface in this console + carries a colour transition for its hover state: without it, flipping the + theme animates all of them at once and the whole page wipes over 150ms + instead of switching. + */ + - -
-
-
- - } /> - + + + + } /> + } /> + } /> + } /> + + + }> + + + + } + > + } /> + + - - - } + + + + } /> - } /> - - - - } + + + + } + /> + + + + } /> - + + } /> - {/* - Board routes gate on the narrowest permission that makes the page - worth opening — a read, in every case but enrollment, which has - nothing to show someone who cannot mint a token. Controls inside - each page gate themselves; see src/utils/permissions.js. - */} + - + + } /> + - + + } /> - + + } /> - + + } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - -
-
-
-
+ + } /> + + {/* Paths from the previous app, kept so live bookmarks land somewhere. */} + } /> + } /> + } /> + } /> + + } /> + + + + +
+
); } - -export default App; diff --git a/src/api/client.ts b/src/api/client.ts index ab2fe76..935a252 100644 --- a/src/api/client.ts +++ b/src/api/client.ts @@ -1,14 +1,14 @@ /** - * The single HTTP entry point for the app. + * The single HTTP entry point for the console. * * Paths, parameters, request bodies and response shapes all come from * src/api/schema.d.ts, which is generated from LensBridgeBackend/openapi.yaml. * Regenerate with `npm run api:generate` -- never edit the schema by hand. * * Everything routes through `authFetch` below, which owns access-token refresh. - * Previously only AuthService.makeRequest did this; BoardService, DirectUploadService - * and a handful of pages used bare fetch and so simply failed once the access token - * expired, instead of refreshing and retrying. + * Every service in src/services goes through the generated `api` client, so an + * expired access token refreshes and the request replays rather than surfacing + * as a bare 401 halfway through a page load. */ import createClient from "openapi-fetch"; import type { paths } from "./schema"; @@ -148,12 +148,10 @@ export const api = createClient({ export { BASE_URL }; /** - * Authenticated fetch for call sites not yet moved onto the typed client. - * - * Shares `authFetch`, so these get the same credentials and the same - * single-flight refresh -- they are simply not type-checked against the schema. - * Prefer `api` for anything new; this exists so the remaining pages behave - * correctly during the migration rather than after it. + * Authenticated fetch for the one call site not on the typed client: + * `AuthService.makeRequest`. Shares `authFetch`, so it gets the same + * credentials and the same single-flight refresh -- it is simply not + * type-checked against the schema. Use `api` for anything new. */ export function authenticatedFetch(url: string, init: RequestInit = {}): Promise { const headers = new Headers(init.headers); @@ -164,11 +162,3 @@ export function authenticatedFetch(url: string, init: RequestInit = {}): Promise return authFetch(new Request(url, { ...init, headers })); } -/** - * Escape hatch for the few transfers OpenAPI does not describe: presigned R2 - * uploads to a third-party origin, and blob downloads of already-signed URLs. - * Deliberately does not attach LensBridge credentials. - */ -export function rawFetch(input: RequestInfo | URL, init?: RequestInit): Promise { - return fetch(input, init); -} diff --git a/src/api/schema.d.ts b/src/api/schema.d.ts index ea1c932..fd1f1ad 100644 --- a/src/api/schema.d.ts +++ b/src/api/schema.d.ts @@ -288,10 +288,44 @@ export interface paths { get: operations["getBoardEventById"]; put?: never; post?: never; - delete: operations["deleteEvent"]; + delete: operations["deleteEvent_1"]; + options?: never; + head?: never; + patch: operations["updateEvent_1"]; + trace?: never; + }; + "/api/admin/board/events/{eventId}/ticket-event": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** Unlink the tCketManage event from a board event */ + delete: operations["unlinkTicketEvent"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/board/events/{eventId}/ticket-event/{tcketEventId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + /** Link a tCketManage event to a board event */ + put: operations["linkTicketEvent"]; + post?: never; + delete?: never; options?: never; head?: never; - patch: operations["updateEvent"]; + patch?: never; trace?: never; }; "/api/admin/board/posters": { @@ -422,14 +456,15 @@ export interface paths { patch: operations["updateSocial"]; trace?: never; }; - "/api/admin/board/weekly-content": { + "/api/admin/board/uploads": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get: operations["getAllWeeklyContent"]; + /** Page through every upload, any approval state */ + get: operations["getAdminUploads"]; put?: never; post?: never; delete?: never; @@ -438,14 +473,14 @@ export interface paths { patch?: never; trace?: never; }; - "/api/admin/board/weekly-content/year/{year}": { + "/api/admin/board/uploads/approved": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get: operations["getWeeklyContentByYear"]; + get: operations["getApprovedUploads"]; put?: never; post?: never; delete?: never; @@ -454,58 +489,55 @@ export interface paths { patch?: never; trace?: never; }; - "/api/admin/board/weekly-content/{year}/{weekNumber}": { + "/api/admin/board/uploads/featured": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** Fetch weekly content as an administrator */ - get: operations["getAdminWeeklyContent"]; - put: operations["saveWeeklyContent"]; + get: operations["getFeaturedUploads"]; + put?: never; post?: never; - delete: operations["deleteWeeklyContent"]; + delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/admin/create-event": { + "/api/admin/board/uploads/pending": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; + get: operations["getPendingUploads"]; put?: never; - /** Create a media event */ - post: operations["createMediaEvent"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/admin/events": { + "/api/admin/board/uploads/{uploadId}": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** List every event, including unpublished ones */ - get: operations["getAllEventsForAdmin"]; + get?: never; put?: never; post?: never; - delete?: never; + delete: operations["deleteUpload"]; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/admin/feature-upload/{uploadId}": { + "/api/admin/board/uploads/{uploadId}/approval": { parameters: { query?: never; header?: never; @@ -514,48 +546,46 @@ export interface paths { }; get?: never; put?: never; - post: operations["featureUpload"]; - delete?: never; + post?: never; + delete: operations["unapproveUpload"]; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/admin/permissions": { + "/api/admin/board/uploads/{uploadId}/approve": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** List every permission, for building a direct-grant picker */ - get: operations["getAvailablePermissions"]; + get?: never; put?: never; - post?: never; + post: operations["approveUpload"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/admin/roles": { + "/api/admin/board/uploads/{uploadId}/feature": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** List assignable roles with the permissions each one confers */ - get: operations["getAvailableRoles"]; + get?: never; put?: never; - post?: never; + post: operations["featureUpload"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/admin/upload/{uploadId}": { + "/api/admin/board/uploads/{uploadId}/featured": { parameters: { query?: never; header?: never; @@ -564,86 +594,108 @@ export interface paths { }; get?: never; put?: never; - post: operations["approveUpload"]; - delete: operations["deleteUpload"]; + post?: never; + delete: operations["unfeatureUpload"]; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/admin/upload/{uploadId}/approval": { + "/api/admin/board/weekly-content": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; + get: operations["getAllWeeklyContent"]; put?: never; post?: never; - delete: operations["unapproveUpload"]; + delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/admin/upload/{uploadId}/featured": { + "/api/admin/board/weekly-content/year/{year}": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; + get: operations["getWeeklyContentByYear"]; put?: never; post?: never; - delete: operations["unfeatureUpload"]; + delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/admin/uploads": { + "/api/admin/board/weekly-content/{year}/{weekNumber}": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** Page through every upload, any approval state */ - get: operations["getAdminUploads"]; - put?: never; + /** Fetch weekly content as an administrator */ + get: operations["getAdminWeeklyContent"]; + put: operations["saveWeeklyContent"]; post?: never; - delete?: never; + delete: operations["deleteWeeklyContent"]; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/admin/uploads/approved": { + "/api/admin/minbar/prayer-spaces": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get: operations["getApprovedUploads"]; + /** List every prayer space, regardless of audience */ + get: operations["listAdminPrayerSpaces"]; put?: never; - post?: never; + /** Create a prayer space */ + post: operations["createPrayerSpace"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/admin/uploads/featured": { + "/api/admin/minbar/prayer-spaces/{id}": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get: operations["getFeaturedUploads"]; + /** Fetch one prayer space as an administrator */ + get: operations["getAdminPrayerSpace"]; + put?: never; + post?: never; + /** Delete a prayer space and its directions */ + delete: operations["deletePrayerSpace"]; + options?: never; + head?: never; + /** Update a prayer space; omitted fields are left unchanged */ + patch: operations["updatePrayerSpace"]; + trace?: never; + }; + "/api/admin/permissions": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List every permission, for building a direct-grant picker */ + get: operations["getAvailablePermissions"]; put?: never; post?: never; delete?: never; @@ -652,14 +704,15 @@ export interface paths { patch?: never; trace?: never; }; - "/api/admin/uploads/pending": { + "/api/admin/roles": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get: operations["getPendingUploads"]; + /** List assignable roles with the permissions each one confers */ + get: operations["getAvailableRoles"]; put?: never; post?: never; delete?: never; @@ -1011,32 +1064,15 @@ export interface paths { patch?: never; trace?: never; }; - "/api/events": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** List publicly visible events */ - get: operations["getPublicEvents"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/api/events/{id}": { + "/api/minbar/events": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** Fetch a single publicly visible event */ - get: operations["getPublicEventById"]; + /** List events for a one-month window, enriched with ticket availability when tCketManage is enabled */ + get: operations["getMinbarEvents"]; put?: never; post?: never; delete?: never; @@ -1045,15 +1081,15 @@ export interface paths { patch?: never; trace?: never; }; - "/api/gallery": { + "/api/minbar/prayer-spaces": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** Page through approved gallery media */ - get: operations["getGalleryUploads"]; + /** List prayer spaces for an audience, with directions */ + get: operations["getMinbarPrayerSpaces"]; put?: never; post?: never; delete?: never; @@ -1062,14 +1098,15 @@ export interface paths { patch?: never; trace?: never; }; - "/api/gallery/event/{eventId}": { + "/api/minbar/prayer-spaces/{id}": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get: operations["getGalleryByEvent"]; + /** Fetch a single prayer space with directions */ + get: operations["getMinbarPrayerSpaceById"]; put?: never; post?: never; delete?: never; @@ -1176,14 +1213,14 @@ export interface paths { patch?: never; trace?: never; }; - "/api/upload/event/{eventId}": { + "/api/tcket/email-jobs/{jobId}": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get: operations["getUploadsByEvent"]; + get: operations["getJob"]; put?: never; post?: never; delete?: never; @@ -1192,23 +1229,23 @@ export interface paths { patch?: never; trace?: never; }; - "/api/upload/limits": { + "/api/tcket/events": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get: operations["getUploadLimits"]; + get: operations["getAllEvents"]; put?: never; - post?: never; + post: operations["createEvent"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/upload/{eventId}/direct/complete": { + "/api/tcket/events/full": { parameters: { query?: never; header?: never; @@ -1217,14 +1254,30 @@ export interface paths { }; get?: never; put?: never; - post: operations["completeDirectUpload"]; + post: operations["createFullEvent"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/upload/{eventId}/direct/presign": { + "/api/tcket/events/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getEventById"]; + put: operations["updateEvent"]; + post?: never; + delete: operations["deleteEvent"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/tcket/events/{id}/imports": { parameters: { query?: never; header?: never; @@ -1233,846 +1286,3327 @@ export interface paths { }; get?: never; put?: never; - post: operations["generatePresignedUploadUrl"]; + post: operations["importAttendees"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/upload/{uploadId}": { + "/api/tcket/events/{id}/ticket-types": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get: operations["getUploadById"]; + get: operations["getTicketTypesByEvent"]; put?: never; - post?: never; + post: operations["createTicketType"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/user/profile": { + "/api/tcket/events/{id}/tickets": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get: operations["getUserProfile"]; + get: operations["getTicketsByEvent"]; put?: never; post?: never; delete?: never; options?: never; head?: never; - patch: operations["updateUserProfile"]; + patch?: never; trace?: never; }; - "/api/user/stats": { + "/api/tcket/events/{id}/tickets/resend": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get: operations["getUserStats"]; + get?: never; put?: never; - post?: never; + post: operations["resendAllTickets"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/user/uploads": { + "/api/tcket/events/{id}/tickets/send-missing": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get: operations["getUserUploads"]; + get?: never; put?: never; - post?: never; + post: operations["sendMissingTickets"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/user/uploads/{uploadId}": { + "/api/tcket/events/{id}/zones": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; + get: operations["getZonesByEvent"]; put?: never; - post?: never; - delete: operations["deleteUserUpload"]; + post: operations["addZoneToEvent"]; + delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; -} -export type webhooks = Record; -export interface components { - schemas: { - AdminUploadDto: { - anon?: boolean; - approved?: boolean; - /** @enum {string} */ - contentType?: "image" | "video" | "audio" | "document"; - /** Format: date-time */ - createdDate?: string; - displayName?: string; - /** Format: uuid */ - eventId?: string; - eventName?: string; - featured?: boolean; - fileName?: string; - fileUrl?: string; - instagramHandle?: string; - secureUrl?: string; - thumbnailUrl?: string; - uploadDescription?: string; - /** Format: uuid */ - uploadedBy?: string; - uploaderEmail?: string; - uploaderFirstName?: string; - uploaderFullName?: string; - uploaderLastName?: string; - uploaderStudentNumber?: string; - /** Format: uuid */ - uuid?: string; - }; - AgendaFrameConfig: Omit & { - days?: components["schemas"]["DayBucket"][]; - heading?: string; - } & { - /** - * @description discriminator enum property added by openapi-typescript - * @enum {string} - */ - type: "agenda"; + "/api/tcket/orders": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - AgentEnrollRequest: { - agentVersion?: string; - hardwareModel?: string; - hostname: string; - publicKey: string; - token: string; + get: operations["getOrders"]; + put?: never; + post: operations["createOrder_1"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/tcket/orders/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - AgentEnrollResponse: { - /** Format: uuid */ - deviceId?: string; - websocketUrl?: string; + get: operations["getOrder_1"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/tcket/orders/{id}/cancel": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - AuditEventDto: { - /** @enum {string} */ - action?: "APPROVE_UPLOAD" | "UNAPPROVE_UPLOAD" | "DELETE_UPLOAD" | "FEATURE_UPLOAD" | "UNFEATURE_UPLOAD" | "CREATE_EVENT" | "UPDATE_EVENT" | "DELETE_EVENT" | "CREATE_CALENDAR_EVENT" | "UPDATE_CALENDAR_EVENT" | "DELETE_CALENDAR_EVENT" | "CREATE_POSTER" | "UPDATE_POSTER" | "DELETE_POSTER" | "CREATE_SOCIAL" | "UPDATE_SOCIAL" | "DELETE_SOCIAL" | "SAVE_WEEKLY_CONTENT" | "DELETE_WEEKLY_CONTENT" | "UPDATE_BOARD_CONFIG" | "UPDATE_BOARD_TICKER" | "REFRESH_BOARDS" | "ISSUE_ENROLLMENT_TOKEN" | "REVOKE_DEVICE" | "ISSUE_DEVICE_COMMAND" | "PROMOTE_USER" | "DEMOTE_USER" | "DISABLE_USER" | "ENABLE_USER" | "VIEW_AUDIT_LOGS" | "EXPORT_DATA" | "SYSTEM_MAINTENANCE" | "VERIFY_USER" | "UNVERIFY_USER" | "RESET_USER_PASSWORD" | "TRIGGER_PASSWORD_RESET_EMAIL" | "ADD_USER_ROLE" | "REMOVE_USER_ROLE" | "GRANT_PERMISSION" | "REVOKE_PERMISSION" | "ADD_USER" | "REMOVE_USER" | "UPDATE_USER"; - adminEmail?: string; - /** Format: uuid */ - adminId?: string; - adminName?: string; - details?: string; - /** Format: uuid */ - id?: string; - ipAddress?: string; + get?: never; + put?: never; + post: operations["cancelOrder_1"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/tcket/orders/{id}/confirm-manual-payment": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["confirmManualPayment"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/tcket/payments/mock/{orderId}/complete": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["completeMockPayment"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/tcket/payments/{providerId}/webhook": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["webhook"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/tcket/scans/count/ticket/{ticketId}/zone/{zoneId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getTicketZoneEntryCount"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/tcket/scans/event/{eventId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getScanHistoryForEvent"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/tcket/scans/scan": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["scanTicket"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/tcket/scans/scan-qr": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["scanByQr"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/tcket/scans/ticket/{ticketId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getScanHistoryForTicket"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/tcket/scans/validate": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["validateTicketForZone"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/tcket/scans/zone/{zoneId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getScanHistoryForZone"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/tcket/ticket-types": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getAllTicketTypes"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/tcket/ticket-types/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getTicketTypeById"]; + put: operations["updateTicketType"]; + post?: never; + delete: operations["deleteTicketType"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/tcket/tickets": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["createTicket"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/tcket/tickets/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getTicketById"]; + put: operations["updateTicket"]; + post?: never; + delete: operations["deleteTicket"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/tcket/tickets/{id}/resend": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["resendTicket"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/tcket/zones": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getAllZones"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/tcket/zones/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getZoneById"]; + put: operations["updateZone"]; + post?: never; + delete: operations["deleteZone"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/upload/event/{eventId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getUploadsByEvent"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/upload/limits": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getUploadLimits"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/upload/{eventId}/direct/complete": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["completeDirectUpload"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/upload/{eventId}/direct/presign": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["generatePresignedUploadUrl"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/upload/{uploadId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getUploadById"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/user/orders": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getUserOrders"]; + put?: never; + post: operations["createOrder"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/user/orders/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getOrder"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/user/orders/{id}/cancel": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["cancelOrder"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/user/profile": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getUserProfile"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch: operations["updateUserProfile"]; + trace?: never; + }; + "/api/user/tickets": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getUserTickets"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/user/uploads": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getUserUploads"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/user/uploads/{uploadId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete: operations["deleteUserUpload"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record; +export interface components { + schemas: { + AddZoneRequest: { + zoneName: string; + }; + AdminUploadDto: { + anon?: boolean; + approved?: boolean; + /** @enum {string} */ + contentType?: "image" | "video" | "audio" | "document"; + /** Format: date-time */ + createdDate?: string; + displayName?: string; + /** Format: uuid */ + eventId?: string; + eventName?: string; + featured?: boolean; + fileName?: string; + fileUrl?: string; + instagramHandle?: string; + secureUrl?: string; + thumbnailUrl?: string; + uploadDescription?: string; + /** Format: uuid */ + uploadedBy?: string; + uploaderEmail?: string; + uploaderFirstName?: string; + uploaderFullName?: string; + uploaderLastName?: string; + uploaderStudentNumber?: string; + /** Format: uuid */ + uuid?: string; + }; + AgendaFrameConfig: Omit & { + days?: components["schemas"]["DayBucket"][]; + heading?: string; + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "agenda"; + }; + AgentEnrollRequest: { + agentVersion?: string; + hardwareModel?: string; + hostname: string; + publicKey: string; + token: string; + }; + AgentEnrollResponse: { + /** Format: uuid */ + deviceId?: string; + websocketUrl?: string; + }; + AuditEventDto: { + /** @enum {string} */ + action?: "APPROVE_UPLOAD" | "UNAPPROVE_UPLOAD" | "DELETE_UPLOAD" | "FEATURE_UPLOAD" | "UNFEATURE_UPLOAD" | "CREATE_EVENT" | "UPDATE_EVENT" | "DELETE_EVENT" | "CREATE_CALENDAR_EVENT" | "UPDATE_CALENDAR_EVENT" | "DELETE_CALENDAR_EVENT" | "LINK_TICKET_EVENT" | "UNLINK_TICKET_EVENT" | "CREATE_POSTER" | "UPDATE_POSTER" | "DELETE_POSTER" | "CREATE_PRAYER_SPACE" | "UPDATE_PRAYER_SPACE" | "DELETE_PRAYER_SPACE" | "CREATE_SOCIAL" | "UPDATE_SOCIAL" | "DELETE_SOCIAL" | "SAVE_WEEKLY_CONTENT" | "DELETE_WEEKLY_CONTENT" | "UPDATE_BOARD_CONFIG" | "UPDATE_BOARD_TICKER" | "REFRESH_BOARDS" | "ISSUE_ENROLLMENT_TOKEN" | "REVOKE_DEVICE" | "ISSUE_DEVICE_COMMAND" | "PROMOTE_USER" | "DEMOTE_USER" | "DISABLE_USER" | "ENABLE_USER" | "VIEW_AUDIT_LOGS" | "EXPORT_DATA" | "SYSTEM_MAINTENANCE" | "VERIFY_USER" | "UNVERIFY_USER" | "RESET_USER_PASSWORD" | "TRIGGER_PASSWORD_RESET_EMAIL" | "ADD_USER_ROLE" | "REMOVE_USER_ROLE" | "GRANT_PERMISSION" | "REVOKE_PERMISSION" | "ADD_USER" | "REMOVE_USER" | "UPDATE_USER"; + adminEmail?: string; + /** Format: uuid */ + adminId?: string; + adminName?: string; + details?: string; + /** Format: uuid */ + id?: string; + ipAddress?: string; + /** Format: uuid */ + targetEntityId?: string; + /** @enum {string} */ + targetEntityType?: "USER" | "UPLOAD" | "MUSALLAH_BOARD" | "DEVICE" | "PRAYER_SPACE" | "EVENT"; + /** Format: date-time */ + timestamp?: string; + userAgent?: string; + }; + BoardEvent: { + allDay?: boolean; + allowUploads?: boolean; + /** @enum {string} */ + audience?: "brothers" | "sisters" | "both"; + description?: string; + /** Format: date-time */ + endTime?: string; + event?: components["schemas"]["Event"]; + /** Format: uuid */ + id?: string; + location?: string; + name?: string; + /** Format: date-time */ + startTime?: string; + }; + BoardingPassView: { + attendeeName?: string; + eventDate?: string; + /** Format: uuid */ + eventId?: string; + eventTitle?: string; + location?: string; + qrPayload?: string; + startTime?: string; + status?: string; + /** Format: uuid */ + ticketId?: string; + ticketType?: string; + }; + ChangePasswordRequest: { + currentPassword: string; + newPassword: string; + }; + CommandIssuedResponse: { + /** Format: uuid */ + commandId?: string; + /** Format: uuid */ + deviceId?: string; + /** Format: date-time */ + expiresAt?: string; + /** Format: date-time */ + issuedAt?: string; + kind?: string; + /** @enum {string} */ + status?: "PENDING" | "DELIVERED" | "ACKED" | "RUNNING" | "SUCCEEDED" | "FAILED" | "TIMEOUT" | "REJECTED" | "EXPIRED"; + }; + CommandView: { + /** Format: date-time */ + ackedAt?: string; + /** Format: int32 */ + deadlineMs?: number; + /** Format: date-time */ + deliveredAt?: string; + /** Format: uuid */ + deviceId?: string; + errorMessage?: string; + /** Format: date-time */ + expiresAt?: string; + /** Format: date-time */ + finishedAt?: string; + /** Format: uuid */ + id?: string; + /** Format: date-time */ + issuedAt?: string; + issuedBy?: string; + kind?: string; + output?: components["schemas"]["JsonNode"]; + payload?: components["schemas"]["JsonNode"]; + /** Format: date-time */ + startedAt?: string; + /** @enum {string} */ + status?: "PENDING" | "DELIVERED" | "ACKED" | "RUNNING" | "SUCCEEDED" | "FAILED" | "TIMEOUT" | "REJECTED" | "EXPIRED"; + }; + CreateCalendarEventRequest: { + allDay?: boolean; + allowUploads?: boolean; + /** @enum {string} */ + audience: "brothers" | "sisters" | "both"; + description: string; + /** Format: int64 */ + endEpochMs: number; + location: string; + name: string; + /** Format: int64 */ + startEpochMs: number; + }; + CreateEventRequest: { + description: string; + location: string; + name: string; + /** Format: date-time */ + time: string; + }; + CreateFullEventRequest: { + description: string; + location: string; + name: string; + ticketTypes?: components["schemas"]["WizardTicketTypeRequest"][]; + /** Format: date-time */ + time: string; + zones: components["schemas"]["WizardZoneRequest"][]; + }; + CreateOrderRequest: { + /** Format: email */ + buyerEmail: string; + /** Format: uuid */ + eventId: string; + items: components["schemas"]["OrderItemRequest"][]; + providerId?: string; + }; + CreatePosterRequest: { + /** @enum {string} */ + audience: "brothers" | "sisters" | "both"; + /** Format: int32 */ + duration?: number; + /** Format: date-time */ + endTime: string; + /** Format: binary */ + imageFile: string; + signupUrl?: string; + /** Format: date-time */ + startTime: string; + title: string; + }; + CreatePrayerSpaceRequest: { + amenities?: string[]; + /** @enum {string} */ + audience?: "brothers" | "sisters" | "both"; + building: string; + /** Format: int32 */ + capacity?: number; + directions?: string; + entranceDescription?: string; + entranceName?: string; + floor?: string; + imageUrl?: string; + /** Format: double */ + latitude?: number; + /** Format: double */ + longitude?: number; + mapsUrl?: string; + name: string; + notes?: string; + roomInfo?: string; + startingPoint?: string; + steps?: components["schemas"]["DirectionStepRequest"][]; + tag: string; + tips?: string[]; + /** @enum {string} */ + type: "brothers" | "sisters" | "multifaith" | "reflection"; + /** Format: int32 */ + walkTimeMinutes?: number; + }; + CreatePromotableSocialMediaRequest: { + /** @enum {string} */ + audience: "brothers" | "sisters" | "both"; + /** Format: int32 */ + duration?: number; + footerText: string; + handle?: string; + headerText: string; + heroText: string; + name: string; + /** @enum {string} */ + type: "instagram" | "youtube" | "tiktok" | "whatsapp" | "other"; + url: string; + }; + CreateTicketRequest: { + /** Format: email */ + email: string; + /** Format: uuid */ + eventId: string; + firstName: string; + lastName: string; + sendEmail?: boolean; + /** Format: uuid */ + ticketTypeId: string; + }; + CreateTicketTypeRequest: { + entitlements?: components["schemas"]["ZoneEntitlementRequest"][]; + isActive?: boolean; + name: string; + price: number; + /** Format: date-time */ + salesEndAt?: string; + /** Format: date-time */ + salesStartAt?: string; + }; + CreateUserRequest: { + /** @enum {string} */ + audience: "brothers" | "sisters" | "both"; + /** Format: email */ + email: string; + firstName: string; + lastName: string; + /** @description Optional. Omit it to create a disabled account and email the user a password reset link; supply it to create an account that can sign in right away. */ + password?: string; + }; + DayBucket: { + /** Format: date */ + date?: string; + events?: components["schemas"]["EventView"][]; + }; + DeviceConfig: { + /** Format: int32 */ + agendaDurationSeconds?: number; + darkModeAfterIsha?: boolean; + enableScrollingMessage?: boolean; + /** Format: uuid */ + id?: string; + location?: components["schemas"]["Location"]; + /** Format: int32 */ + nextPrayerDurationSeconds?: number; + scrollingMessages?: string[]; + }; + DeviceSummary: { + agentVersion?: string; + /** @enum {string} */ + audience?: "brothers" | "sisters" | "both"; + displayName?: string; + /** Format: date-time */ + enrolledAt?: string; + hardwareModel?: string; + /** Format: uuid */ + id?: string; + /** Format: date-time */ + lastHeartbeat?: string; + lastSeenIp?: string; + /** Format: date-time */ + revokedAt?: string; + }; + DirectionStepRequest: { + instruction: string; + subtext?: string; + }; + DirectionStepView: { + /** Format: uuid */ + id?: string; + instruction?: string; + /** Format: int32 */ + order?: number; + subtext?: string; + }; + EmailJobAccepted: { + /** Format: uuid */ + jobId?: string; + /** Format: int32 */ + total?: number; + }; + EmailJobStatus: { + /** Format: int32 */ + failed?: number; + /** Format: date-time */ + finishedAt?: string; + /** Format: uuid */ + jobId?: string; + lastEmail?: string; + lastSuccess?: boolean; + /** Format: int32 */ + processed?: number; + /** Format: int32 */ + sent?: number; + /** Format: date-time */ + startedAt?: string; + /** @enum {string} */ + state?: "RUNNING" | "COMPLETED"; + /** Format: int32 */ + total?: number; + type?: string; + }; + Event: { + description: string; + /** Format: uuid */ + id?: string; + location: string; + name: string; + /** Format: date-time */ + time: string; + }; + EventResponse: { + description?: string; + /** Format: uuid */ + id?: string; + location?: string; + name?: string; + /** Format: date-time */ + time?: string; + zones?: components["schemas"]["ZoneResponse"][]; + }; + EventView: { + allDay?: boolean; + allowUploads?: boolean; + /** @enum {string} */ + audience?: "brothers" | "sisters" | "both"; + description?: string; + /** Format: date-time */ + endTime?: string; + /** Format: uuid */ + id?: string; + location?: string; + name?: string; + /** Format: date-time */ + startTime?: string; + /** Format: uuid */ + ticketEventId?: string; + ticketTypes?: components["schemas"]["MinbarTicketTypeView"][]; + }; + FrameConfig: { + type: string; + }; + FrameDefinition: { + /** Format: int32 */ + durationInSeconds?: number; + frameConfig?: components["schemas"]["AgendaFrameConfig"] | components["schemas"]["IslamicQuoteFrameConfig"] | components["schemas"]["JummahFrameConfig"] | components["schemas"]["NextPrayerFrameConfig"] | components["schemas"]["PosterFrameConfig"] | components["schemas"]["PromotableSocialMediaFrameConfig"]; + frameId?: string; + /** @enum {string} */ + frameType?: "poster" | "next_prayer" | "agenda" | "jummah" | "islamic_quote" | "socials"; + }; + FullEventResponse: { + event?: components["schemas"]["EventResponse"]; + ticketTypes?: components["schemas"]["TicketTypeResponse"][]; + }; + ImportConfig: { + /** Format: uuid */ + defaultTicketTypeId?: string; + /** Format: int32 */ + emailColumn: number; + /** Format: int32 */ + firstNameColumn: number; + hasHeaderRow?: boolean; + /** Format: int32 */ + lastNameColumn: number; + /** Format: int32 */ + ticketTypeColumn?: number; + }; + ImportResult: { + errors?: components["schemas"]["RowError"][]; + /** Format: int32 */ + imported?: number; + }; + IslamicQuote: { + arabic?: string; + /** Format: int32 */ + durationSeconds?: number; + /** Format: uuid */ + id?: string; + /** @enum {string} */ + kind?: "VERSE" | "HADITH"; + reference?: string; + translation?: string; + transliteration?: string; + }; + IslamicQuoteFrameConfig: Omit & { + arabic?: string; + /** @enum {string} */ + kind?: "VERSE" | "HADITH"; + reference?: string; + translation?: string; + transliteration?: string; + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "islamic_quote"; + }; + IssueCommandRequest: { + /** Format: int32 */ + deadlineMs?: number; + kind: string; + payload?: components["schemas"]["JsonNode"]; + /** Format: int32 */ + ttlSeconds?: number; + }; + IssueEnrollmentTokenRequest: { + /** @enum {string} */ + audience: "brothers" | "sisters" | "both"; + displayName: string; + /** Format: int32 */ + expiresInMinutes?: number; + }; + IssueEnrollmentTokenResponse: { + /** Format: date-time */ + expiresAt?: string; + token?: string; + /** Format: uuid */ + tokenId?: string; + }; + JsonNode: unknown; + JummahFrameConfig: Omit & { + prayers?: components["schemas"]["JummahSlot"][]; + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "jummah"; + }; + JummahPrayer: { + /** Format: uuid */ + id?: string; + khatib?: string; + prayerTime?: string; + room?: string; + }; + JummahSlot: { + khatib?: string; + prayerTime?: string; + room?: string; + }; + JwtResponse: { + email?: string; + firstName?: string; + /** Format: uuid */ + id?: string; + lastName?: string; + permissions?: string[]; + refreshToken?: string; + roles?: string[]; + token?: string; + type?: string; + }; + Location: { + city?: string; + country?: string; + /** Format: double */ + latitude?: number; + /** Format: double */ + longitude?: number; + /** @enum {string} */ + method?: "KARACHI" | "ISNA" | "MWL" | "MAKKAH" | "EGYPT" | "TEHRAN" | "GULF" | "KUWAIT" | "QATAR" | "SINGAPORE" | "FRANCE" | "TURKEY" | "RUSSIA" | "DUBAI"; + timezone?: string; + }; + LoginRequest: { + /** Format: email */ + email: string; + password: string; + }; + MessageResponse: { + message?: string; + }; + MinbarCreateOrderRequest: { + /** Format: uuid */ + eventId: string; + items: components["schemas"]["OrderItemRequest"][]; + }; + MinbarOrderItemResponse: { + attendeeFirstName?: string; + attendeeLastName?: string; + /** Format: uuid */ + eventId?: string; + ticketTypeName?: string; + unitPrice?: number; + }; + MinbarOrderResponse: { + amountTotal?: number; + buyerEmail?: string; + /** Format: date-time */ + createdAt?: string; + currency?: string; + /** Format: date-time */ + expiresAt?: string; + /** Format: uuid */ + id?: string; + items?: components["schemas"]["MinbarOrderItemResponse"][]; + /** Format: date-time */ + paidAt?: string; + payment?: components["schemas"]["MinbarPaymentResponse"]; + referenceCode?: string; + status?: string; + }; + MinbarPaymentResponse: { + details?: { + [key: string]: string; + }; + instructions?: string; + redirectUrl?: string; + type?: string; + }; + MinbarTicketTypeView: { + /** Format: uuid */ + id?: string; + name?: string; + price?: number; + /** Format: date-time */ + salesEndAt?: string; + /** Format: date-time */ + salesStartAt?: string; + soldOut?: boolean; + }; + MusallahBoardPayload: { + deviceConfig?: components["schemas"]["DeviceConfig"]; + frames?: components["schemas"]["FrameDefinition"][]; + weather?: components["schemas"]["JsonNode"]; + }; + NextPrayerFrameConfig: Omit & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "next_prayer"; + }; + OrderItemRequest: { + /** Format: email */ + attendeeEmail: string; + attendeeFirstName: string; + attendeeLastName: string; + /** Format: uuid */ + ticketTypeId: string; + }; + OrderItemResponse: { + attendeeEmail?: string; + attendeeFirstName?: string; + attendeeLastName?: string; + /** Format: uuid */ + eventId?: string; + /** Format: uuid */ + id?: string; + /** Format: uuid */ + ticketTypeId?: string; + ticketTypeName?: string; + unitPrice?: number; + }; + OrderResponse: { + amountTotal?: number; + buyerEmail?: string; + /** Format: date-time */ + createdAt?: string; + currency?: string; + /** Format: uuid */ + eventId?: string; + /** Format: date-time */ + expiresAt?: string; + externalRef?: string; + /** Format: uuid */ + id?: string; + items?: components["schemas"]["OrderItemResponse"][]; + /** Format: date-time */ + paidAt?: string; + payment?: components["schemas"]["PaymentResponse"]; + providerId?: string; + referenceCode?: string; + status?: string; + }; + PageAdminUploadDto: { + content?: components["schemas"]["AdminUploadDto"][]; + empty?: boolean; + first?: boolean; + last?: boolean; + /** Format: int32 */ + number?: number; + /** Format: int32 */ + numberOfElements?: number; + pageable?: components["schemas"]["Pageable"]; + /** Format: int32 */ + size?: number; + sort?: components["schemas"]["SortObject"]; + /** Format: int64 */ + totalElements?: number; + /** Format: int32 */ + totalPages?: number; + }; + PageAuditEventDto: { + content?: components["schemas"]["AuditEventDto"][]; + empty?: boolean; + first?: boolean; + last?: boolean; + /** Format: int32 */ + number?: number; + /** Format: int32 */ + numberOfElements?: number; + pageable?: components["schemas"]["Pageable"]; + /** Format: int32 */ + size?: number; + sort?: components["schemas"]["SortObject"]; + /** Format: int64 */ + totalElements?: number; + /** Format: int32 */ + totalPages?: number; + }; + PageEventResponse: { + content?: components["schemas"]["EventResponse"][]; + empty?: boolean; + first?: boolean; + last?: boolean; + /** Format: int32 */ + number?: number; + /** Format: int32 */ + numberOfElements?: number; + pageable?: components["schemas"]["Pageable"]; + /** Format: int32 */ + size?: number; + sort?: components["schemas"]["SortObject"]; + /** Format: int64 */ + totalElements?: number; + /** Format: int32 */ + totalPages?: number; + }; + PageScanEventResponse: { + content?: components["schemas"]["ScanEventResponse"][]; + empty?: boolean; + first?: boolean; + last?: boolean; + /** Format: int32 */ + number?: number; + /** Format: int32 */ + numberOfElements?: number; + pageable?: components["schemas"]["Pageable"]; + /** Format: int32 */ + size?: number; + sort?: components["schemas"]["SortObject"]; + /** Format: int64 */ + totalElements?: number; + /** Format: int32 */ + totalPages?: number; + }; + PageTicketResponse: { + content?: components["schemas"]["TicketResponse"][]; + empty?: boolean; + first?: boolean; + last?: boolean; + /** Format: int32 */ + number?: number; + /** Format: int32 */ + numberOfElements?: number; + pageable?: components["schemas"]["Pageable"]; + /** Format: int32 */ + size?: number; + sort?: components["schemas"]["SortObject"]; + /** Format: int64 */ + totalElements?: number; + /** Format: int32 */ + totalPages?: number; + }; + PageTicketTypeResponse: { + content?: components["schemas"]["TicketTypeResponse"][]; + empty?: boolean; + first?: boolean; + last?: boolean; + /** Format: int32 */ + number?: number; + /** Format: int32 */ + numberOfElements?: number; + pageable?: components["schemas"]["Pageable"]; + /** Format: int32 */ + size?: number; + sort?: components["schemas"]["SortObject"]; + /** Format: int64 */ + totalElements?: number; + /** Format: int32 */ + totalPages?: number; + }; + PageUploadDto: { + content?: components["schemas"]["UploadDto"][]; + empty?: boolean; + first?: boolean; + last?: boolean; + /** Format: int32 */ + number?: number; + /** Format: int32 */ + numberOfElements?: number; + pageable?: components["schemas"]["Pageable"]; + /** Format: int32 */ + size?: number; + sort?: components["schemas"]["SortObject"]; + /** Format: int64 */ + totalElements?: number; + /** Format: int32 */ + totalPages?: number; + }; + PageUserInfoResponse: { + content?: components["schemas"]["UserInfoResponse"][]; + empty?: boolean; + first?: boolean; + last?: boolean; + /** Format: int32 */ + number?: number; + /** Format: int32 */ + numberOfElements?: number; + pageable?: components["schemas"]["Pageable"]; + /** Format: int32 */ + size?: number; + sort?: components["schemas"]["SortObject"]; + /** Format: int64 */ + totalElements?: number; + /** Format: int32 */ + totalPages?: number; + }; + PageZoneResponse: { + content?: components["schemas"]["ZoneResponse"][]; + empty?: boolean; + first?: boolean; + last?: boolean; + /** Format: int32 */ + number?: number; + /** Format: int32 */ + numberOfElements?: number; + pageable?: components["schemas"]["Pageable"]; + /** Format: int32 */ + size?: number; + sort?: components["schemas"]["SortObject"]; + /** Format: int64 */ + totalElements?: number; + /** Format: int32 */ + totalPages?: number; + }; + Pageable: { + /** Format: int32 */ + page?: number; + /** Format: int32 */ + size?: number; + sort?: string[]; + }; + PaymentResponse: { + details?: { + [key: string]: string; + }; + instructions?: string; + providerRef?: string; + redirectUrl?: string; + type?: string; + }; + PermissionResponse: { + authority?: string; + domain?: string; + name?: string; + }; + Poster: { + /** @enum {string} */ + audience?: "brothers" | "sisters" | "both"; + /** Format: int32 */ + duration?: number; + /** Format: date-time */ + endTime?: string; + /** Format: uuid */ + id?: string; + image?: string; + signupUrl?: string; + /** Format: date-time */ + startTime?: string; + title?: string; + }; + PosterFrameConfig: Omit & { + posterUrl?: string; + signupUrl?: string; + title?: string; + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "poster"; + }; + PrayerSpaceView: { + amenities?: string[]; + /** @enum {string} */ + audience?: "brothers" | "sisters" | "both"; + building?: string; + /** Format: int32 */ + capacity?: number; + directions?: string; + entranceDescription?: string; + entranceName?: string; + floor?: string; + /** Format: uuid */ + id?: string; + imageUrl?: string; + /** Format: double */ + latitude?: number; + /** Format: double */ + longitude?: number; + mapsUrl?: string; + name?: string; + notes?: string; + roomInfo?: string; + startingPoint?: string; + steps?: components["schemas"]["DirectionStepView"][]; + tag?: string; + tips?: string[]; + /** @enum {string} */ + type?: "brothers" | "sisters" | "multifaith" | "reflection"; + /** Format: int32 */ + walkTimeMinutes?: number; + }; + PresignedUploadResponse: { + contentType?: string; + /** Format: uuid */ + eventId?: string; + expectedSha256?: string; + /** Format: int32 */ + expiresInMinutes?: number; + method?: string; + objectKey?: string; + uploadUrl?: string; + }; + PromotableSocialMedia: { + /** @enum {string} */ + audience?: "brothers" | "sisters" | "both"; + /** Format: int32 */ + duration?: number; + footerText?: string; + handle?: string; + headerText?: string; + heroText?: string; + /** Format: uuid */ + id?: string; + name?: string; + /** @enum {string} */ + type?: "instagram" | "youtube" | "tiktok" | "whatsapp" | "other"; + url?: string; + }; + PromotableSocialMediaFrameConfig: Omit & { + footerText?: string; + handle?: string; + headerText?: string; + heroText?: string; + /** @enum {string} */ + socialType?: "instagram" | "youtube" | "tiktok" | "whatsapp" | "other"; + url?: string; + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "socials"; + }; + QrScanRequest: { + qrPayload: string; + /** Format: uuid */ + zoneId: string; + }; + QuoteEntry: { + arabic?: string; + /** Format: int32 */ + durationSeconds?: number; + /** @enum {string} */ + kind?: "VERSE" | "HADITH"; + reference?: string; + translation?: string; + transliteration?: string; + }; + ResetPasswordRequest: { + newPassword: string; + token: string; + }; + RoleDefinitionResponse: { + description?: string; + name?: string; + permissions?: string[]; + }; + RowError: { + reason?: string; + /** Format: int32 */ + row?: number; + }; + ScanEventResponse: { + /** Format: uuid */ + id?: string; + /** Format: uuid */ + ticketId?: string; + /** Format: date-time */ + timestamp?: string; + /** Format: uuid */ + zoneId?: string; + zoneName?: string; + }; + ScanRequest: { + /** Format: uuid */ + ticketId: string; + /** Format: uuid */ + zoneId: string; + }; + ScanResult: { + message?: string; + /** @enum {string} */ + outcome?: "SUCCESS" | "INVALID_QR" | "NO_ZONE_ENTITLEMENT" | "ENTRY_LIMIT_REACHED"; + scanEvent?: components["schemas"]["ScanEventResponse"]; + success?: boolean; + }; + SignupRequest: { + /** @enum {string} */ + audience: "brothers" | "sisters" | "both"; + /** Format: email */ + email: string; + firstName: string; + lastName: string; + password: string; + }; + SortObject: { + empty?: boolean; + sorted?: boolean; + unsorted?: boolean; + }; + TicketResponse: { + email?: string; /** Format: uuid */ - targetEntityId?: string; + eventId?: string; + firstName?: string; + holderRef?: string; + /** Format: uuid */ + id?: string; + lastName?: string; + /** Format: date-time */ + lastTicketSent?: string; + ticketType?: components["schemas"]["TicketTypeResponse"]; + }; + TicketTypeResponse: { + /** Format: int32 */ + capacity?: number; + /** Format: date-time */ + createdAt?: string; + entitlements?: components["schemas"]["ZoneEntitlementResponse"][]; + /** Format: uuid */ + eventId?: string; + /** Format: uuid */ + id?: string; + isActive?: boolean; + name?: string; + price?: number; + /** Format: int32 */ + reservedCount?: number; + /** Format: date-time */ + salesEndAt?: string; + /** Format: date-time */ + salesStartAt?: string; + }; + TokenRefreshRequest: { + refreshToken: string; + }; + TokenRefreshResponse: { + accessToken?: string; + refreshToken?: string; + tokenType?: string; + }; + TokenValidationResponse: { + email?: string; + firstName?: string; + /** Format: uuid */ + id?: string; + lastName?: string; + permissions?: string[]; + roles?: string[]; + valid?: boolean; + verified?: boolean; + }; + UpdateBoardConfigRequest: { + /** Format: int32 */ + agendaDurationSeconds?: number; + darkModeAfterIsha?: boolean; + enableScrollingMessage?: boolean; + location?: components["schemas"]["Location"]; + /** Format: int32 */ + nextPrayerDurationSeconds?: number; + scrollingMessages?: string[]; + }; + UpdateCalendarEventRequest: { + allDay?: boolean; + allowUploads?: boolean; /** @enum {string} */ - targetEntityType?: "USER" | "UPLOAD" | "MUSALLAH_BOARD" | "DEVICE" | "EVENT"; + audience?: "brothers" | "sisters" | "both"; + description?: string; /** Format: date-time */ - timestamp?: string; - userAgent?: string; + endTime?: string; + location?: string; + name?: string; + /** Format: date-time */ + startTime?: string; + }; + UpdateDeviceRequest: { + /** @enum {string} */ + audience?: "brothers" | "sisters" | "both"; + displayName?: string; + }; + UpdateEventRequest: { + description: string; + location: string; + name: string; + /** Format: date-time */ + time: string; + }; + UpdatePosterRequest: { + /** @enum {string} */ + audience?: "brothers" | "sisters" | "both"; + /** Format: int32 */ + duration?: number; + /** Format: date-time */ + endTime?: string; + signupUrl?: string; + /** Format: date-time */ + startTime?: string; + title?: string; + }; + UpdatePrayerSpaceRequest: { + amenities?: string[]; + /** @enum {string} */ + audience?: "brothers" | "sisters" | "both"; + building?: string; + /** Format: int32 */ + capacity?: number; + clearCoordinates?: boolean; + directions?: string; + entranceDescription?: string; + entranceName?: string; + floor?: string; + imageUrl?: string; + /** Format: double */ + latitude?: number; + /** Format: double */ + longitude?: number; + mapsUrl?: string; + name?: string; + notes?: string; + roomInfo?: string; + startingPoint?: string; + steps?: components["schemas"]["DirectionStepRequest"][]; + tag?: string; + tips?: string[]; + /** @enum {string} */ + type?: "brothers" | "sisters" | "multifaith" | "reflection"; + /** Format: int32 */ + walkTimeMinutes?: number; + }; + UpdateProfileRequest: { + firstName?: string; + lastName?: string; + }; + UpdatePromotableSocialMediaRequest: { + /** @enum {string} */ + audience?: "brothers" | "sisters" | "both"; + /** Format: int32 */ + duration?: number; + footerText?: string; + handle?: string; + headerText?: string; + heroText?: string; + name?: string; + /** @enum {string} */ + type?: "instagram" | "youtube" | "tiktok" | "whatsapp" | "other"; + url?: string; + }; + UpdateTickerRequest: { + enableScrollingMessage?: boolean; + scrollingMessages?: string[]; + }; + UpdateTicketRequest: { + email?: string; + firstName?: string; + lastName?: string; + /** Format: uuid */ + ticketTypeId?: string; + }; + UpdateTicketTypeRequest: { + entitlements?: components["schemas"]["ZoneEntitlementRequest"][]; + isActive?: boolean; + name: string; + price: number; + /** Format: date-time */ + salesEndAt?: string; + /** Format: date-time */ + salesStartAt?: string; + }; + UpdateZoneRequest: { + description: string; + name: string; + }; + UploadCompletionResponse: { + /** Format: uuid */ + eventId?: string; + /** Format: int64 */ + fileSize?: number; + objectKey?: string; + /** Format: uuid */ + uploadId?: string; + verified?: boolean; + }; + UploadDto: { + anon?: boolean; + approved?: boolean; + /** @enum {string} */ + contentType?: "image" | "video" | "audio" | "document"; + /** Format: date-time */ + createdDate?: string; + /** Format: uuid */ + eventId?: string; + eventName?: string; + featured?: boolean; + fileName?: string; + fileUrl?: string; + instagramHandle?: string; + thumbnailUrl?: string; + uploadDescription?: string; + /** Format: uuid */ + uploadedBy?: string; + /** Format: uuid */ + uuid?: string; + }; + UploadLimitsResponse: { + allowedContentTypes?: string[]; + /** Format: int32 */ + dailyLimit?: number; + /** Format: int64 */ + maxSizeBytes?: number; + /** Format: int64 */ + maxSizeMB?: number; + role?: string; + /** Format: int64 */ + uploadsRemaining?: number; + /** Format: int64 */ + uploadsToday?: number; + /** Format: int32 */ + videoMaxDurationSeconds?: number; + }; + UserInfoResponse: { + /** @enum {string} */ + audience?: "brothers" | "sisters" | "both"; + directPermissions?: string[]; + effectivePermissions?: string[]; + email?: string; + firstName?: string; + /** Format: uuid */ + id?: string; + lastName?: string; + roles?: ("USER" | "BOARD_VIEWER" | "BOARD_EDITOR" | "BOARD_ADMIN" | "TCKET_SCANNER" | "TCKET_MANAGER" | "TCKET_ADMIN" | "ROOT")[]; + verified?: boolean; + }; + ValidateRequest: { + /** Format: uuid */ + ticketId: string; + /** Format: uuid */ + zoneId: string; + }; + ValidateResetTokenRequest: { + token: string; + }; + ValidationResult: { + message?: string; + valid?: boolean; + }; + VerifyEmailRequest: { + token: string; + }; + VerifyUserRequest: { + /** Format: uuid */ + userId: string; + }; + WeeklyContent: { + /** Format: uuid */ + id?: string; + jummahPrayers?: components["schemas"]["JummahPrayer"][]; + quotes?: components["schemas"]["IslamicQuote"][]; + /** Format: int32 */ + weekNumber?: number; + /** Format: int32 */ + year?: number; + }; + WeeklyContentRequest: { + jummahPrayers?: components["schemas"]["JummahSlot"][]; + quotes?: components["schemas"]["QuoteEntry"][]; + }; + WizardEntitlementRequest: { + /** Format: int32 */ + maxEntries?: number; + zoneKey: string; + }; + WizardTicketTypeRequest: { + /** Format: int32 */ + capacity?: number; + entitlements?: components["schemas"]["WizardEntitlementRequest"][]; + isActive?: boolean; + name: string; + price: number; + /** Format: date-time */ + salesEndAt?: string; + /** Format: date-time */ + salesStartAt?: string; + }; + WizardZoneRequest: { + description?: string; + key: string; + name: string; + }; + ZoneEntitlementRequest: { + /** Format: int32 */ + maxEntries?: number; + /** Format: uuid */ + zoneId: string; + }; + ZoneEntitlementResponse: { + /** Format: int32 */ + maxEntries?: number; + /** Format: uuid */ + zoneId?: string; + zoneName?: string; + }; + ZoneResponse: { + description?: string; + /** Format: uuid */ + eventId?: string; + /** Format: uuid */ + id?: string; + name?: string; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export interface operations { + getAuditEvents: { + parameters: { + query?: { + /** @description Zero-based page index (0..N) */ + page?: number; + /** @description The size of the page to be returned */ + size?: number; + /** @description Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. */ + sort?: string[]; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PageAuditEventDto"]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; + }; + }; + getAuditEventsByAction: { + parameters: { + query?: { + /** @description Zero-based page index (0..N) */ + page?: number; + /** @description The size of the page to be returned */ + size?: number; + /** @description Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. */ + sort?: string[]; + }; + header?: never; + path: { + action: "APPROVE_UPLOAD" | "UNAPPROVE_UPLOAD" | "DELETE_UPLOAD" | "FEATURE_UPLOAD" | "UNFEATURE_UPLOAD" | "CREATE_EVENT" | "UPDATE_EVENT" | "DELETE_EVENT" | "CREATE_CALENDAR_EVENT" | "UPDATE_CALENDAR_EVENT" | "DELETE_CALENDAR_EVENT" | "LINK_TICKET_EVENT" | "UNLINK_TICKET_EVENT" | "CREATE_POSTER" | "UPDATE_POSTER" | "DELETE_POSTER" | "CREATE_PRAYER_SPACE" | "UPDATE_PRAYER_SPACE" | "DELETE_PRAYER_SPACE" | "CREATE_SOCIAL" | "UPDATE_SOCIAL" | "DELETE_SOCIAL" | "SAVE_WEEKLY_CONTENT" | "DELETE_WEEKLY_CONTENT" | "UPDATE_BOARD_CONFIG" | "UPDATE_BOARD_TICKER" | "REFRESH_BOARDS" | "ISSUE_ENROLLMENT_TOKEN" | "REVOKE_DEVICE" | "ISSUE_DEVICE_COMMAND" | "PROMOTE_USER" | "DEMOTE_USER" | "DISABLE_USER" | "ENABLE_USER" | "VIEW_AUDIT_LOGS" | "EXPORT_DATA" | "SYSTEM_MAINTENANCE" | "VERIFY_USER" | "UNVERIFY_USER" | "RESET_USER_PASSWORD" | "TRIGGER_PASSWORD_RESET_EMAIL" | "ADD_USER_ROLE" | "REMOVE_USER_ROLE" | "GRANT_PERMISSION" | "REVOKE_PERMISSION" | "ADD_USER" | "REMOVE_USER" | "UPDATE_USER"; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PageAuditEventDto"]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; + }; + }; + getAvailableAuditActions: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": ("APPROVE_UPLOAD" | "UNAPPROVE_UPLOAD" | "DELETE_UPLOAD" | "FEATURE_UPLOAD" | "UNFEATURE_UPLOAD" | "CREATE_EVENT" | "UPDATE_EVENT" | "DELETE_EVENT" | "CREATE_CALENDAR_EVENT" | "UPDATE_CALENDAR_EVENT" | "DELETE_CALENDAR_EVENT" | "LINK_TICKET_EVENT" | "UNLINK_TICKET_EVENT" | "CREATE_POSTER" | "UPDATE_POSTER" | "DELETE_POSTER" | "CREATE_PRAYER_SPACE" | "UPDATE_PRAYER_SPACE" | "DELETE_PRAYER_SPACE" | "CREATE_SOCIAL" | "UPDATE_SOCIAL" | "DELETE_SOCIAL" | "SAVE_WEEKLY_CONTENT" | "DELETE_WEEKLY_CONTENT" | "UPDATE_BOARD_CONFIG" | "UPDATE_BOARD_TICKER" | "REFRESH_BOARDS" | "ISSUE_ENROLLMENT_TOKEN" | "REVOKE_DEVICE" | "ISSUE_DEVICE_COMMAND" | "PROMOTE_USER" | "DEMOTE_USER" | "DISABLE_USER" | "ENABLE_USER" | "VIEW_AUDIT_LOGS" | "EXPORT_DATA" | "SYSTEM_MAINTENANCE" | "VERIFY_USER" | "UNVERIFY_USER" | "RESET_USER_PASSWORD" | "TRIGGER_PASSWORD_RESET_EMAIL" | "ADD_USER_ROLE" | "REMOVE_USER_ROLE" | "GRANT_PERMISSION" | "REVOKE_PERMISSION" | "ADD_USER" | "REMOVE_USER" | "UPDATE_USER")[]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; + }; + }; + getAuditEventsByDateRange: { + parameters: { + query: { + start: string; + end: string; + /** @description Zero-based page index (0..N) */ + page?: number; + /** @description The size of the page to be returned */ + size?: number; + /** @description Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. */ + sort?: string[]; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PageAuditEventDto"]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; + }; + }; + getFailedOperations: { + parameters: { + query?: { + /** @description Zero-based page index (0..N) */ + page?: number; + /** @description The size of the page to be returned */ + size?: number; + /** @description Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. */ + sort?: string[]; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PageAuditEventDto"]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; + }; + }; + getUploadAuditHistory: { + parameters: { + query?: never; + header?: never; + path: { + uploadId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AuditEventDto"][]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; + }; + }; + getAllBoardConfigs: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DeviceConfig"][]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; + }; + }; + getAdminBoardConfig: { + parameters: { + query?: never; + header?: never; + path: { + deviceId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DeviceConfig"]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; }; - BoardEvent: { - allDay?: boolean; - /** @enum {string} */ - audience?: "brothers" | "sisters" | "both"; - description?: string; - /** Format: date-time */ - endTime?: string; - /** Format: uuid */ - id?: string; - location?: string; - name?: string; - /** Format: date-time */ - startTime?: string; + }; + saveBoardConfig: { + parameters: { + query?: never; + header?: never; + path: { + deviceId: string; + }; + cookie?: never; }; - ChangePasswordRequest: { - currentPassword: string; - newPassword: string; + requestBody: { + content: { + "application/json": components["schemas"]["DeviceConfig"]; + }; }; - CommandIssuedResponse: { - /** Format: uuid */ - commandId?: string; - /** Format: uuid */ - deviceId?: string; - /** Format: date-time */ - expiresAt?: string; - /** Format: date-time */ - issuedAt?: string; - kind?: string; - /** @enum {string} */ - status?: "PENDING" | "DELIVERED" | "ACKED" | "RUNNING" | "SUCCEEDED" | "FAILED" | "TIMEOUT" | "REJECTED" | "EXPIRED"; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DeviceConfig"]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; + }; + }; + updateBoardConfig: { + parameters: { + query?: never; + header?: never; + path: { + deviceId: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UpdateBoardConfigRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DeviceConfig"]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; + }; + }; + updateBoardTicker: { + parameters: { + query?: never; + header?: never; + path: { + deviceId: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UpdateTickerRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DeviceConfig"]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; + }; + }; + listDevices: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DeviceSummary"][]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; + }; + }; + issueEnrollmentToken: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["IssueEnrollmentTokenRequest"]; + }; + }; + responses: { + /** @description Token issued; the plaintext is returned exactly once */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["IssueEnrollmentTokenResponse"]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; + }; + }; + getDevice: { + parameters: { + query?: never; + header?: never; + path: { + deviceId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Device found */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DeviceSummary"]; + }; + }; + /** @description No device with that id */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; + }; + }; + updateDevice: { + parameters: { + query?: never; + header?: never; + path: { + deviceId: string; + }; + cookie?: never; }; - CommandView: { - /** Format: date-time */ - ackedAt?: string; - /** Format: int32 */ - deadlineMs?: number; - /** Format: date-time */ - deliveredAt?: string; - /** Format: uuid */ - deviceId?: string; - errorMessage?: string; - /** Format: date-time */ - expiresAt?: string; - /** Format: date-time */ - finishedAt?: string; - /** Format: uuid */ - id?: string; - /** Format: date-time */ - issuedAt?: string; - issuedBy?: string; - kind?: string; - output?: components["schemas"]["JsonNode"]; - payload?: components["schemas"]["JsonNode"]; - /** Format: date-time */ - startedAt?: string; - /** @enum {string} */ - status?: "PENDING" | "DELIVERED" | "ACKED" | "RUNNING" | "SUCCEEDED" | "FAILED" | "TIMEOUT" | "REJECTED" | "EXPIRED"; + requestBody: { + content: { + "application/json": components["schemas"]["UpdateDeviceRequest"]; + }; }; - CreateCalendarEventRequest: { - allDay?: boolean; - /** @enum {string} */ - audience: "brothers" | "sisters" | "both"; - description: string; - /** Format: int64 */ - endEpochMs: number; - location: string; - name: string; - /** Format: int64 */ - startEpochMs: number; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DeviceSummary"]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; }; - CreatePosterRequest: { - /** @enum {string} */ - audience: "brothers" | "sisters" | "both"; - /** Format: int32 */ - duration?: number; - /** Format: date-time */ - endTime: string; - /** Format: binary */ - imageFile: string; - signupUrl?: string; - /** Format: date-time */ - startTime: string; - title: string; + }; + listDeviceCommands: { + parameters: { + query?: never; + header?: never; + path: { + deviceId: string; + }; + cookie?: never; }; - CreatePromotableSocialMediaRequest: { - /** @enum {string} */ - audience: "brothers" | "sisters" | "both"; - /** Format: int32 */ - duration?: number; - footerText: string; - handle?: string; - headerText: string; - heroText: string; - name: string; - /** @enum {string} */ - type: "instagram" | "youtube" | "tiktok" | "whatsapp" | "other"; - url: string; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CommandView"][]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; }; - CreateUserRequest: { - /** Format: email */ - email: string; - firstName: string; - lastName: string; - /** @description Optional. Omit it to create a disabled account and email the user a password reset link; supply it to create an account that can sign in right away. */ - password?: string; - studentNumber: string; + }; + issueDeviceCommand: { + parameters: { + query?: never; + header?: never; + path: { + deviceId: string; + }; + cookie?: never; }; - DayBucket: { - /** Format: date */ - date?: string; - events?: components["schemas"]["EventView"][]; + requestBody: { + content: { + "application/json": components["schemas"]["IssueCommandRequest"]; + }; }; - DeviceConfig: { - /** Format: int32 */ - agendaDurationSeconds?: number; - darkModeAfterIsha?: boolean; - enableScrollingMessage?: boolean; - /** Format: uuid */ - id?: string; - location?: components["schemas"]["Location"]; - /** Format: int32 */ - nextPrayerDurationSeconds?: number; - scrollingMessages?: string[]; + responses: { + /** @description Command accepted and queued for delivery */ + 202: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CommandIssuedResponse"]; + }; + }; + /** @description Caller lacks the permission for this command kind */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; }; - DeviceSummary: { - agentVersion?: string; - /** @enum {string} */ - audience?: "brothers" | "sisters" | "both"; - displayName?: string; - /** Format: date-time */ - enrolledAt?: string; - hardwareModel?: string; - /** Format: uuid */ - id?: string; - /** Format: date-time */ - lastHeartbeat?: string; - lastSeenIp?: string; - /** Format: date-time */ - revokedAt?: string; + }; + revokeDevice: { + parameters: { + query?: never; + header?: never; + path: { + deviceId: string; + }; + cookie?: never; }; - EventView: { - allDay?: boolean; - description?: string; - /** Format: date-time */ - endTime?: string; - location?: string; - name?: string; - /** Format: date-time */ - startTime?: string; + requestBody?: never; + responses: { + /** @description Device revoked */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DeviceSummary"]; + }; + }; + /** @description No device with that id */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; }; - FrameConfig: { - type: string; + }; + getBoardEvents: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BoardEvent"][]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; }; - FrameDefinition: { - /** Format: int32 */ - durationInSeconds?: number; - frameConfig?: components["schemas"]["AgendaFrameConfig"] | components["schemas"]["IslamicQuoteFrameConfig"] | components["schemas"]["JummahFrameConfig"] | components["schemas"]["NextPrayerFrameConfig"] | components["schemas"]["PosterFrameConfig"] | components["schemas"]["PromotableSocialMediaFrameConfig"]; - frameId?: string; - /** @enum {string} */ - frameType?: "poster" | "next_prayer" | "agenda" | "jummah" | "islamic_quote" | "socials"; + }; + createBoardEvent: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - GalleryItemDto: { - author?: string; - date?: string; - event?: string; - featured?: boolean; - id?: string; - src?: string; - thumbnail?: string; - title?: string; - type?: string; + requestBody: { + content: { + "application/json": components["schemas"]["CreateCalendarEventRequest"]; + }; }; - IslamicQuote: { - arabic?: string; - /** Format: int32 */ - durationSeconds?: number; - /** Format: uuid */ - id?: string; - /** @enum {string} */ - kind?: "VERSE" | "HADITH"; - reference?: string; - translation?: string; - transliteration?: string; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BoardEvent"]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; }; - IslamicQuoteFrameConfig: Omit & { - arabic?: string; - /** @enum {string} */ - kind?: "VERSE" | "HADITH"; - reference?: string; - translation?: string; - transliteration?: string; - } & { - /** - * @description discriminator enum property added by openapi-typescript - * @enum {string} - */ - type: "islamic_quote"; + }; + getEventsForAudience: { + parameters: { + query: { + audience: "brothers" | "sisters" | "both"; + }; + header?: never; + path?: never; + cookie?: never; }; - IssueCommandRequest: { - /** Format: int32 */ - deadlineMs?: number; - kind: string; - payload?: components["schemas"]["JsonNode"]; - /** Format: int32 */ - ttlSeconds?: number; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BoardEvent"][]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; }; - IssueEnrollmentTokenRequest: { - /** @enum {string} */ - audience: "brothers" | "sisters" | "both"; - displayName: string; - /** Format: int32 */ - expiresInMinutes?: number; + }; + getBoardEventById: { + parameters: { + query?: never; + header?: never; + path: { + eventId: string; + }; + cookie?: never; }; - IssueEnrollmentTokenResponse: { - /** Format: date-time */ - expiresAt?: string; - token?: string; - /** Format: uuid */ - tokenId?: string; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BoardEvent"]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; }; - JsonNode: unknown; - JummahFrameConfig: Omit & { - prayers?: components["schemas"]["JummahSlot"][]; - } & { - /** - * @description discriminator enum property added by openapi-typescript - * @enum {string} - */ - type: "jummah"; + }; + deleteEvent_1: { + parameters: { + query?: never; + header?: never; + path: { + eventId: string; + }; + cookie?: never; }; - JummahPrayer: { - /** Format: uuid */ - id?: string; - khatib?: string; - prayerTime?: string; - room?: string; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; }; - JummahSlot: { - khatib?: string; - prayerTime?: string; - room?: string; + }; + updateEvent_1: { + parameters: { + query?: never; + header?: never; + path: { + eventId: string; + }; + cookie?: never; }; - JwtResponse: { - email?: string; - firstName?: string; - /** Format: uuid */ - id?: string; - lastName?: string; - permissions?: string[]; - refreshToken?: string; - roles?: string[]; - token?: string; - type?: string; + requestBody: { + content: { + "application/json": components["schemas"]["UpdateCalendarEventRequest"]; + }; }; - Location: { - city?: string; - country?: string; - /** Format: double */ - latitude?: number; - /** Format: double */ - longitude?: number; - /** @enum {string} */ - method?: "KARACHI" | "ISNA" | "MWL" | "MAKKAH" | "EGYPT" | "TEHRAN" | "GULF" | "KUWAIT" | "QATAR" | "SINGAPORE" | "FRANCE" | "TURKEY" | "RUSSIA" | "DUBAI"; - timezone?: string; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BoardEvent"]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; }; - LoginRequest: { - /** Format: email */ - email: string; - password: string; + }; + unlinkTicketEvent: { + parameters: { + query?: never; + header?: never; + path: { + eventId: string; + }; + cookie?: never; }; - MediaEvent: { - /** Format: date-time */ - date?: string; - /** Format: uuid */ - id?: string; - name?: string; - /** @enum {string} */ - status?: "UPCOMING" | "ONGOING" | "PAST"; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BoardEvent"]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; }; - MessageResponse: { - message?: string; + }; + linkTicketEvent: { + parameters: { + query?: never; + header?: never; + path: { + eventId: string; + tcketEventId: string; + }; + cookie?: never; }; - MusallahBoardPayload: { - deviceConfig?: components["schemas"]["DeviceConfig"]; - frames?: components["schemas"]["FrameDefinition"][]; - weather?: components["schemas"]["JsonNode"]; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BoardEvent"]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; }; - NextPrayerFrameConfig: Omit & { - /** - * @description discriminator enum property added by openapi-typescript - * @enum {string} - */ - type: "next_prayer"; + }; + getAllPosters: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - PageAdminUploadDto: { - content?: components["schemas"]["AdminUploadDto"][]; - empty?: boolean; - first?: boolean; - last?: boolean; - /** Format: int32 */ - number?: number; - /** Format: int32 */ - numberOfElements?: number; - pageable?: components["schemas"]["Pageable"]; - /** Format: int32 */ - size?: number; - sort?: components["schemas"]["SortObject"]; - /** Format: int64 */ - totalElements?: number; - /** Format: int32 */ - totalPages?: number; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Poster"][]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; }; - PageAuditEventDto: { - content?: components["schemas"]["AuditEventDto"][]; - empty?: boolean; - first?: boolean; - last?: boolean; - /** Format: int32 */ - number?: number; - /** Format: int32 */ - numberOfElements?: number; - pageable?: components["schemas"]["Pageable"]; - /** Format: int32 */ - size?: number; - sort?: components["schemas"]["SortObject"]; - /** Format: int64 */ - totalElements?: number; - /** Format: int32 */ - totalPages?: number; + }; + createPoster: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - PageGalleryItemDto: { - content?: components["schemas"]["GalleryItemDto"][]; - empty?: boolean; - first?: boolean; - last?: boolean; - /** Format: int32 */ - number?: number; - /** Format: int32 */ - numberOfElements?: number; - pageable?: components["schemas"]["Pageable"]; - /** Format: int32 */ - size?: number; - sort?: components["schemas"]["SortObject"]; - /** Format: int64 */ - totalElements?: number; - /** Format: int32 */ - totalPages?: number; + requestBody?: { + content: { + "multipart/form-data": components["schemas"]["CreatePosterRequest"]; + }; }; - PageUploadDto: { - content?: components["schemas"]["UploadDto"][]; - empty?: boolean; - first?: boolean; - last?: boolean; - /** Format: int32 */ - number?: number; - /** Format: int32 */ - numberOfElements?: number; - pageable?: components["schemas"]["Pageable"]; - /** Format: int32 */ - size?: number; - sort?: components["schemas"]["SortObject"]; - /** Format: int64 */ - totalElements?: number; - /** Format: int32 */ - totalPages?: number; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Poster"]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; }; - PageUserInfoResponse: { - content?: components["schemas"]["UserInfoResponse"][]; - empty?: boolean; - first?: boolean; - last?: boolean; - /** Format: int32 */ - number?: number; - /** Format: int32 */ - numberOfElements?: number; - pageable?: components["schemas"]["Pageable"]; - /** Format: int32 */ - size?: number; - sort?: components["schemas"]["SortObject"]; - /** Format: int64 */ - totalElements?: number; - /** Format: int32 */ - totalPages?: number; + }; + getPostersForAudience: { + parameters: { + query: { + audience: "brothers" | "sisters" | "both"; + }; + header?: never; + path?: never; + cookie?: never; }; - Pageable: { - /** Format: int32 */ - page?: number; - /** Format: int32 */ - size?: number; - sort?: string[]; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Poster"][]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; + }; + }; + getPosterById: { + parameters: { + query?: never; + header?: never; + path: { + posterId: string; + }; + cookie?: never; }; - PermissionResponse: { - authority?: string; - domain?: string; - name?: string; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Poster"]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; }; - Poster: { - /** @enum {string} */ - audience?: "brothers" | "sisters" | "both"; - /** Format: int32 */ - duration?: number; - /** Format: date-time */ - endTime?: string; - /** Format: uuid */ - id?: string; - image?: string; - signupUrl?: string; - /** Format: date-time */ - startTime?: string; - title?: string; + }; + deletePoster: { + parameters: { + query?: never; + header?: never; + path: { + posterId: string; + }; + cookie?: never; }; - PosterFrameConfig: Omit & { - posterUrl?: string; - signupUrl?: string; - title?: string; - } & { - /** - * @description discriminator enum property added by openapi-typescript - * @enum {string} - */ - type: "poster"; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; }; - PresignedUploadResponse: { - contentType?: string; - /** Format: uuid */ - eventId?: string; - expectedSha256?: string; - /** Format: int32 */ - expiresInMinutes?: number; - method?: string; - objectKey?: string; - uploadUrl?: string; + }; + updatePoster: { + parameters: { + query?: never; + header?: never; + path: { + posterId: string; + }; + cookie?: never; }; - PromotableSocialMedia: { - /** @enum {string} */ - audience?: "brothers" | "sisters" | "both"; - /** Format: int32 */ - duration?: number; - footerText?: string; - handle?: string; - headerText?: string; - heroText?: string; - /** Format: uuid */ - id?: string; - name?: string; - /** @enum {string} */ - type?: "instagram" | "youtube" | "tiktok" | "whatsapp" | "other"; - url?: string; + requestBody: { + content: { + "application/json": components["schemas"]["UpdatePosterRequest"]; + }; }; - PromotableSocialMediaFrameConfig: Omit & { - footerText?: string; - handle?: string; - headerText?: string; - heroText?: string; - /** @enum {string} */ - socialType?: "instagram" | "youtube" | "tiktok" | "whatsapp" | "other"; - url?: string; - } & { - /** - * @description discriminator enum property added by openapi-typescript - * @enum {string} - */ - type: "socials"; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Poster"]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; }; - QuoteEntry: { - arabic?: string; - /** Format: int32 */ - durationSeconds?: number; - /** @enum {string} */ - kind?: "VERSE" | "HADITH"; - reference?: string; - translation?: string; - transliteration?: string; + }; + updatePosterImage: { + parameters: { + query?: never; + header?: never; + path: { + posterId: string; + }; + cookie?: never; }; - ResetPasswordRequest: { - newPassword: string; - token: string; + requestBody?: { + content: { + "multipart/form-data": { + /** Format: binary */ + image: string; + }; + }; }; - RoleDefinitionResponse: { - description?: string; - name?: string; - permissions?: string[]; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Poster"]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; }; - SignupRequest: { - /** Format: email */ - email: string; - firstName: string; - lastName: string; - password: string; - studentNumber: string; + }; + refreshSignboard: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - SortObject: { - empty?: boolean; - sorted?: boolean; - unsorted?: boolean; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; }; - TokenRefreshRequest: { - refreshToken: string; + }; + getAllSocials: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - TokenRefreshResponse: { - accessToken?: string; - refreshToken?: string; - tokenType?: string; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PromotableSocialMedia"][]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; }; - TokenValidationResponse: { - email?: string; - firstName?: string; - /** Format: uuid */ - id?: string; - lastName?: string; - permissions?: string[]; - roles?: string[]; - valid?: boolean; - verified?: boolean; + }; + createSocial: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - UpdateBoardConfigRequest: { - /** Format: int32 */ - agendaDurationSeconds?: number; - darkModeAfterIsha?: boolean; - enableScrollingMessage?: boolean; - location?: components["schemas"]["Location"]; - /** Format: int32 */ - nextPrayerDurationSeconds?: number; - scrollingMessages?: string[]; + requestBody: { + content: { + "application/json": components["schemas"]["CreatePromotableSocialMediaRequest"]; + }; }; - UpdateCalendarEventRequest: { - allDay?: boolean; - /** @enum {string} */ - audience?: "brothers" | "sisters" | "both"; - description?: string; - /** Format: date-time */ - endTime?: string; - location?: string; - name?: string; - /** Format: date-time */ - startTime?: string; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PromotableSocialMedia"]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; }; - UpdateDeviceRequest: { - /** @enum {string} */ - audience?: "brothers" | "sisters" | "both"; - displayName?: string; + }; + getSocialsForAudience: { + parameters: { + query: { + audience: "brothers" | "sisters" | "both"; + }; + header?: never; + path?: never; + cookie?: never; }; - UpdatePosterRequest: { - /** @enum {string} */ - audience?: "brothers" | "sisters" | "both"; - /** Format: int32 */ - duration?: number; - /** Format: date-time */ - endTime?: string; - signupUrl?: string; - /** Format: date-time */ - startTime?: string; - title?: string; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PromotableSocialMedia"][]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; }; - UpdateProfileRequest: { - firstName?: string; - lastName?: string; - studentNumber?: string; + }; + getSocialById: { + parameters: { + query?: never; + header?: never; + path: { + socialId: string; + }; + cookie?: never; }; - UpdatePromotableSocialMediaRequest: { - /** @enum {string} */ - audience?: "brothers" | "sisters" | "both"; - /** Format: int32 */ - duration?: number; - footerText?: string; - handle?: string; - headerText?: string; - heroText?: string; - name?: string; - /** @enum {string} */ - type?: "instagram" | "youtube" | "tiktok" | "whatsapp" | "other"; - url?: string; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PromotableSocialMedia"]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; }; - UpdateTickerRequest: { - enableScrollingMessage?: boolean; - scrollingMessages?: string[]; + }; + deleteSocial: { + parameters: { + query?: never; + header?: never; + path: { + socialId: string; + }; + cookie?: never; }; - UploadCompletionResponse: { - /** Format: uuid */ - eventId?: string; - /** Format: int64 */ - fileSize?: number; - objectKey?: string; - /** Format: uuid */ - uploadId?: string; - verified?: boolean; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; }; - UploadDto: { - anon?: boolean; - approved?: boolean; - /** @enum {string} */ - contentType?: "image" | "video" | "audio" | "document"; - /** Format: date-time */ - createdDate?: string; - /** Format: uuid */ - eventId?: string; - eventName?: string; - featured?: boolean; - fileName?: string; - fileUrl?: string; - instagramHandle?: string; - thumbnailUrl?: string; - uploadDescription?: string; - /** Format: uuid */ - uploadedBy?: string; - /** Format: uuid */ - uuid?: string; + }; + updateSocial: { + parameters: { + query?: never; + header?: never; + path: { + socialId: string; + }; + cookie?: never; }; - UploadLimitsResponse: { - allowedContentTypes?: string[]; - /** Format: int32 */ - dailyLimit?: number; - /** Format: int64 */ - maxSizeBytes?: number; - /** Format: int64 */ - maxSizeMB?: number; - role?: string; - /** Format: int64 */ - uploadsRemaining?: number; - /** Format: int64 */ - uploadsToday?: number; - /** Format: int32 */ - videoMaxDurationSeconds?: number; + requestBody: { + content: { + "application/json": components["schemas"]["UpdatePromotableSocialMediaRequest"]; + }; }; - UserInfoResponse: { - directPermissions?: string[]; - effectivePermissions?: string[]; - email?: string; - firstName?: string; - /** Format: uuid */ - id?: string; - lastName?: string; - roles?: ("USER" | "ADMIN" | "BOARD_VIEWER" | "BOARD_EDITOR" | "BOARD_ADMIN" | "ROOT")[]; - studentNumber?: string; - verified?: boolean; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PromotableSocialMedia"]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; }; - UserStatsResponse: { - /** Format: int32 */ - approvedUploads?: number; - /** Format: int32 */ - featuredUploads?: number; - /** Format: int32 */ - pendingUploads?: number; - /** Format: int32 */ - totalUploads?: number; + }; + getAdminUploads: { + parameters: { + query?: { + /** @description Zero-based page index (0..N) */ + page?: number; + /** @description The size of the page to be returned */ + size?: number; + /** @description Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. */ + sort?: string[]; + }; + header?: never; + path?: never; + cookie?: never; }; - ValidateResetTokenRequest: { - token: string; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PageAdminUploadDto"]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; }; - VerifyEmailRequest: { - token: string; + }; + getApprovedUploads: { + parameters: { + query?: { + /** @description Zero-based page index (0..N) */ + page?: number; + /** @description The size of the page to be returned */ + size?: number; + /** @description Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. */ + sort?: string[]; + }; + header?: never; + path?: never; + cookie?: never; }; - VerifyUserRequest: { - /** Format: uuid */ - userId: string; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PageAdminUploadDto"]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; }; - WeeklyContent: { - /** Format: uuid */ - id?: string; - jummahPrayers?: components["schemas"]["JummahPrayer"][]; - quotes?: components["schemas"]["IslamicQuote"][]; - /** Format: int32 */ - weekNumber?: number; - /** Format: int32 */ - year?: number; + }; + getFeaturedUploads: { + parameters: { + query?: { + /** @description Zero-based page index (0..N) */ + page?: number; + /** @description The size of the page to be returned */ + size?: number; + /** @description Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. */ + sort?: string[]; + }; + header?: never; + path?: never; + cookie?: never; }; - WeeklyContentRequest: { - jummahPrayers?: components["schemas"]["JummahSlot"][]; - quotes?: components["schemas"]["QuoteEntry"][]; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PageAdminUploadDto"]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; }; }; - responses: never; - parameters: never; - requestBodies: never; - headers: never; - pathItems: never; -} -export type $defs = Record; -export interface operations { - getAuditEvents: { + getPendingUploads: { parameters: { query?: { /** @description Zero-based page index (0..N) */ @@ -2083,7 +4617,100 @@ export interface operations { sort?: string[]; }; header?: never; - path?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PageAdminUploadDto"]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; + }; + }; + deleteUpload: { + parameters: { + query?: never; + header?: never; + path: { + uploadId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; + }; + }; + unapproveUpload: { + parameters: { + query?: never; + header?: never; + path: { + uploadId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; + }; + }; + approveUpload: { + parameters: { + query?: never; + header?: never; + path: { + uploadId: string; + }; cookie?: never; }; requestBody?: never; @@ -2094,7 +4721,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["PageAuditEventDto"]; + "application/json": components["schemas"]["MessageResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -2108,19 +4735,12 @@ export interface operations { }; }; }; - getAuditEventsByAction: { + featureUpload: { parameters: { - query?: { - /** @description Zero-based page index (0..N) */ - page?: number; - /** @description The size of the page to be returned */ - size?: number; - /** @description Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. */ - sort?: string[]; - }; + query?: never; header?: never; path: { - action: "APPROVE_UPLOAD" | "UNAPPROVE_UPLOAD" | "DELETE_UPLOAD" | "FEATURE_UPLOAD" | "UNFEATURE_UPLOAD" | "CREATE_EVENT" | "UPDATE_EVENT" | "DELETE_EVENT" | "CREATE_CALENDAR_EVENT" | "UPDATE_CALENDAR_EVENT" | "DELETE_CALENDAR_EVENT" | "CREATE_POSTER" | "UPDATE_POSTER" | "DELETE_POSTER" | "CREATE_SOCIAL" | "UPDATE_SOCIAL" | "DELETE_SOCIAL" | "SAVE_WEEKLY_CONTENT" | "DELETE_WEEKLY_CONTENT" | "UPDATE_BOARD_CONFIG" | "UPDATE_BOARD_TICKER" | "REFRESH_BOARDS" | "ISSUE_ENROLLMENT_TOKEN" | "REVOKE_DEVICE" | "ISSUE_DEVICE_COMMAND" | "PROMOTE_USER" | "DEMOTE_USER" | "DISABLE_USER" | "ENABLE_USER" | "VIEW_AUDIT_LOGS" | "EXPORT_DATA" | "SYSTEM_MAINTENANCE" | "VERIFY_USER" | "UNVERIFY_USER" | "RESET_USER_PASSWORD" | "TRIGGER_PASSWORD_RESET_EMAIL" | "ADD_USER_ROLE" | "REMOVE_USER_ROLE" | "GRANT_PERMISSION" | "REVOKE_PERMISSION" | "ADD_USER" | "REMOVE_USER" | "UPDATE_USER"; + uploadId: string; }; cookie?: never; }; @@ -2132,7 +4752,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["PageAuditEventDto"]; + "application/json": components["schemas"]["MessageResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -2146,11 +4766,13 @@ export interface operations { }; }; }; - getAvailableAuditActions: { + unfeatureUpload: { parameters: { query?: never; header?: never; - path?: never; + path: { + uploadId: string; + }; cookie?: never; }; requestBody?: never; @@ -2161,7 +4783,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": ("APPROVE_UPLOAD" | "UNAPPROVE_UPLOAD" | "DELETE_UPLOAD" | "FEATURE_UPLOAD" | "UNFEATURE_UPLOAD" | "CREATE_EVENT" | "UPDATE_EVENT" | "DELETE_EVENT" | "CREATE_CALENDAR_EVENT" | "UPDATE_CALENDAR_EVENT" | "DELETE_CALENDAR_EVENT" | "CREATE_POSTER" | "UPDATE_POSTER" | "DELETE_POSTER" | "CREATE_SOCIAL" | "UPDATE_SOCIAL" | "DELETE_SOCIAL" | "SAVE_WEEKLY_CONTENT" | "DELETE_WEEKLY_CONTENT" | "UPDATE_BOARD_CONFIG" | "UPDATE_BOARD_TICKER" | "REFRESH_BOARDS" | "ISSUE_ENROLLMENT_TOKEN" | "REVOKE_DEVICE" | "ISSUE_DEVICE_COMMAND" | "PROMOTE_USER" | "DEMOTE_USER" | "DISABLE_USER" | "ENABLE_USER" | "VIEW_AUDIT_LOGS" | "EXPORT_DATA" | "SYSTEM_MAINTENANCE" | "VERIFY_USER" | "UNVERIFY_USER" | "RESET_USER_PASSWORD" | "TRIGGER_PASSWORD_RESET_EMAIL" | "ADD_USER_ROLE" | "REMOVE_USER_ROLE" | "GRANT_PERMISSION" | "REVOKE_PERMISSION" | "ADD_USER" | "REMOVE_USER" | "UPDATE_USER")[]; + "application/json": components["schemas"]["MessageResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -2175,18 +4797,9 @@ export interface operations { }; }; }; - getAuditEventsByDateRange: { + getAllWeeklyContent: { parameters: { - query: { - start: string; - end: string; - /** @description Zero-based page index (0..N) */ - page?: number; - /** @description The size of the page to be returned */ - size?: number; - /** @description Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. */ - sort?: string[]; - }; + query?: never; header?: never; path?: never; cookie?: never; @@ -2199,7 +4812,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["PageAuditEventDto"]; + "application/json": components["schemas"]["WeeklyContent"][]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -2213,18 +4826,13 @@ export interface operations { }; }; }; - getFailedOperations: { + getWeeklyContentByYear: { parameters: { - query?: { - /** @description Zero-based page index (0..N) */ - page?: number; - /** @description The size of the page to be returned */ - size?: number; - /** @description Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. */ - sort?: string[]; - }; + query?: never; header?: never; - path?: never; + path: { + year: number; + }; cookie?: never; }; requestBody?: never; @@ -2235,7 +4843,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["PageAuditEventDto"]; + "application/json": components["schemas"]["WeeklyContent"][]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -2249,12 +4857,13 @@ export interface operations { }; }; }; - getUploadAuditHistory: { + getAdminWeeklyContent: { parameters: { query?: never; header?: never; path: { - uploadId: string; + year: number; + weekNumber: number; }; cookie?: never; }; @@ -2266,7 +4875,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["AuditEventDto"][]; + "application/json": components["schemas"]["WeeklyContent"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -2280,14 +4889,21 @@ export interface operations { }; }; }; - getAllBoardConfigs: { + saveWeeklyContent: { parameters: { query?: never; header?: never; - path?: never; + path: { + year: number; + weekNumber: number; + }; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["WeeklyContentRequest"]; + }; + }; responses: { /** @description OK */ 200: { @@ -2295,7 +4911,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["DeviceConfig"][]; + "application/json": components["schemas"]["WeeklyContent"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -2309,12 +4925,13 @@ export interface operations { }; }; }; - getAdminBoardConfig: { + deleteWeeklyContent: { parameters: { query?: never; header?: never; path: { - deviceId: string; + year: number; + weekNumber: number; }; cookie?: never; }; @@ -2326,7 +4943,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["DeviceConfig"]; + "application/json": components["schemas"]["MessageResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -2340,20 +4957,14 @@ export interface operations { }; }; }; - saveBoardConfig: { + listAdminPrayerSpaces: { parameters: { query?: never; header?: never; - path: { - deviceId: string; - }; + path?: never; cookie?: never; }; - requestBody: { - content: { - "application/json": components["schemas"]["DeviceConfig"]; - }; - }; + requestBody?: never; responses: { /** @description OK */ 200: { @@ -2361,7 +4972,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["DeviceConfig"]; + "application/json": components["schemas"]["PrayerSpaceView"][]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -2375,18 +4986,16 @@ export interface operations { }; }; }; - updateBoardConfig: { + createPrayerSpace: { parameters: { query?: never; header?: never; - path: { - deviceId: string; - }; + path?: never; cookie?: never; }; requestBody: { content: { - "application/json": components["schemas"]["UpdateBoardConfigRequest"]; + "application/json": components["schemas"]["CreatePrayerSpaceRequest"]; }; }; responses: { @@ -2396,7 +5005,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["DeviceConfig"]; + "application/json": components["schemas"]["PrayerSpaceView"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -2410,20 +5019,16 @@ export interface operations { }; }; }; - updateBoardTicker: { + getAdminPrayerSpace: { parameters: { query?: never; header?: never; path: { - deviceId: string; + id: string; }; cookie?: never; }; - requestBody: { - content: { - "application/json": components["schemas"]["UpdateTickerRequest"]; - }; - }; + requestBody?: never; responses: { /** @description OK */ 200: { @@ -2431,7 +5036,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["DeviceConfig"]; + "application/json": components["schemas"]["PrayerSpaceView"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -2445,11 +5050,13 @@ export interface operations { }; }; }; - listDevices: { + deletePrayerSpace: { parameters: { query?: never; header?: never; - path?: never; + path: { + id: string; + }; cookie?: never; }; requestBody?: never; @@ -2460,7 +5067,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["DeviceSummary"][]; + "application/json": components["schemas"]["MessageResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -2474,26 +5081,28 @@ export interface operations { }; }; }; - issueEnrollmentToken: { + updatePrayerSpace: { parameters: { query?: never; header?: never; - path?: never; + path: { + id: string; + }; cookie?: never; }; requestBody: { content: { - "application/json": components["schemas"]["IssueEnrollmentTokenRequest"]; + "application/json": components["schemas"]["UpdatePrayerSpaceRequest"]; }; }; responses: { - /** @description Token issued; the plaintext is returned exactly once */ - 201: { + /** @description OK */ + 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["IssueEnrollmentTokenResponse"]; + "application/json": components["schemas"]["PrayerSpaceView"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -2507,28 +5116,26 @@ export interface operations { }; }; }; - getDevice: { + getAvailablePermissions: { parameters: { query?: never; header?: never; - path: { - deviceId: string; - }; + path?: never; cookie?: never; }; requestBody?: never; responses: { - /** @description Device found */ + /** @description OK */ 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["DeviceSummary"]; + "application/json": components["schemas"]["PermissionResponse"][]; }; }; - /** @description No device with that id */ - 404: { + /** @description Request failed; body carries a human-readable message */ + default: { headers: { [name: string]: unknown; }; @@ -2536,6 +5143,26 @@ export interface operations { "application/json": components["schemas"]["MessageResponse"]; }; }; + }; + }; + getAvailableRoles: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RoleDefinitionResponse"][]; + }; + }; /** @description Request failed; body carries a human-readable message */ default: { headers: { @@ -2547,18 +5174,49 @@ export interface operations { }; }; }; - updateDevice: { + createUser: { parameters: { query?: never; header?: never; - path: { - deviceId: string; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateUserRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; }; + }; + }; + verifyUser: { + parameters: { + query?: never; + header?: never; + path?: never; cookie?: never; }; requestBody: { content: { - "application/json": components["schemas"]["UpdateDeviceRequest"]; + "application/json": components["schemas"]["VerifyUserRequest"]; }; }; responses: { @@ -2568,7 +5226,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["DeviceSummary"]; + "application/json": components["schemas"]["MessageResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -2582,16 +5240,20 @@ export interface operations { }; }; }; - listDeviceCommands: { + addRoleToUser: { parameters: { query?: never; header?: never; path: { - deviceId: string; + userId: string; }; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": "USER" | "BOARD_VIEWER" | "BOARD_EDITOR" | "BOARD_ADMIN" | "TCKET_SCANNER" | "TCKET_MANAGER" | "TCKET_ADMIN" | "ROOT"; + }; + }; responses: { /** @description OK */ 200: { @@ -2599,7 +5261,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["CommandView"][]; + "application/json": components["schemas"]["MessageResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -2613,32 +5275,58 @@ export interface operations { }; }; }; - issueDeviceCommand: { + grantUserPermission: { parameters: { query?: never; header?: never; path: { - deviceId: string; + userId: string; }; cookie?: never; }; requestBody: { content: { - "application/json": components["schemas"]["IssueCommandRequest"]; + "application/json": "MEDIA_UPLOAD_SELF" | "MEDIA_UPLOAD_MODERATE" | "MEDIA_UPLOAD_READ" | "BOARD_CONTENT_READ" | "BOARD_POSTER_WRITE" | "BOARD_EVENT_WRITE" | "BOARD_SOCIAL_WRITE" | "BOARD_WEEKLY_WRITE" | "BOARD_TICKER_WRITE" | "BOARD_PRAYER_SPACE_WRITE" | "BOARD_CONFIG_READ" | "BOARD_CONFIG_WRITE" | "BOARD_REFRESH" | "BOARD_DEVICE_READ" | "BOARD_DEVICE_ENROLL" | "BOARD_DEVICE_REVOKE" | "BOARD_TELEMETRY_SUBSCRIBE" | "BOARD_COMMAND_BENIGN" | "BOARD_COMMAND_DISRUPTIVE" | "BOARD_COMMAND_INSPECT" | "TCKET_SCAN" | "TCKET_MANAGE" | "TCKET_ADMIN" | "IAM_USER_READ" | "IAM_USER_WRITE" | "IAM_ROLE_GRANT" | "AUDIT_READ"; }; }; responses: { - /** @description Command accepted and queued for delivery */ - 202: { + /** @description OK */ + 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["CommandIssuedResponse"]; + "application/json": components["schemas"]["MessageResponse"]; }; }; - /** @description Caller lacks the permission for this command kind */ - 403: { + /** @description Request failed; body carries a human-readable message */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; + }; + }; + removeRoleFromUser: { + parameters: { + query?: never; + header?: never; + path: { + userId: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": "USER" | "BOARD_VIEWER" | "BOARD_EDITOR" | "BOARD_ADMIN" | "TCKET_SCANNER" | "TCKET_MANAGER" | "TCKET_ADMIN" | "ROOT"; + }; + }; + responses: { + /** @description OK */ + 200: { headers: { [name: string]: unknown; }; @@ -2657,28 +5345,23 @@ export interface operations { }; }; }; - revokeDevice: { + revokeUserPermission: { parameters: { query?: never; header?: never; path: { - deviceId: string; + userId: string; }; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": "MEDIA_UPLOAD_SELF" | "MEDIA_UPLOAD_MODERATE" | "MEDIA_UPLOAD_READ" | "BOARD_CONTENT_READ" | "BOARD_POSTER_WRITE" | "BOARD_EVENT_WRITE" | "BOARD_SOCIAL_WRITE" | "BOARD_WEEKLY_WRITE" | "BOARD_TICKER_WRITE" | "BOARD_PRAYER_SPACE_WRITE" | "BOARD_CONFIG_READ" | "BOARD_CONFIG_WRITE" | "BOARD_REFRESH" | "BOARD_DEVICE_READ" | "BOARD_DEVICE_ENROLL" | "BOARD_DEVICE_REVOKE" | "BOARD_TELEMETRY_SUBSCRIBE" | "BOARD_COMMAND_BENIGN" | "BOARD_COMMAND_DISRUPTIVE" | "BOARD_COMMAND_INSPECT" | "TCKET_SCAN" | "TCKET_MANAGE" | "TCKET_ADMIN" | "IAM_USER_READ" | "IAM_USER_WRITE" | "IAM_ROLE_GRANT" | "AUDIT_READ"; + }; + }; responses: { - /** @description Device revoked */ + /** @description OK */ 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["DeviceSummary"]; - }; - }; - /** @description No device with that id */ - 404: { headers: { [name: string]: unknown; }; @@ -2697,9 +5380,16 @@ export interface operations { }; }; }; - getBoardEvents: { + getAllUsers: { parameters: { - query?: never; + query?: { + /** @description Zero-based page index (0..N) */ + page?: number; + /** @description The size of the page to be returned */ + size?: number; + /** @description Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. */ + sort?: string[]; + }; header?: never; path?: never; cookie?: never; @@ -2712,7 +5402,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["BoardEvent"][]; + "application/json": components["schemas"]["PageUserInfoResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -2726,7 +5416,7 @@ export interface operations { }; }; }; - createBoardEvent: { + enrollAgent: { parameters: { query?: never; header?: never; @@ -2735,21 +5425,21 @@ export interface operations { }; requestBody: { content: { - "application/json": components["schemas"]["CreateCalendarEventRequest"]; + "application/json": components["schemas"]["AgentEnrollRequest"]; }; }; responses: { - /** @description OK */ + /** @description Device enrolled; identity and websocket URL returned */ 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["BoardEvent"]; + "application/json": components["schemas"]["AgentEnrollResponse"]; }; }; - /** @description Request failed; body carries a human-readable message */ - default: { + /** @description Public key malformed or unsupported */ + 400: { headers: { [name: string]: unknown; }; @@ -2757,26 +5447,13 @@ export interface operations { "application/json": components["schemas"]["MessageResponse"]; }; }; - }; - }; - getEventsForAudience: { - parameters: { - query: { - audience: "brothers" | "sisters" | "both"; - }; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description OK */ - 200: { + /** @description Enrollment token invalid, expired, or already used */ + 401: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["BoardEvent"][]; + "application/json": components["schemas"]["MessageResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -2790,24 +5467,44 @@ export interface operations { }; }; }; - getBoardEventById: { + changePassword: { parameters: { query?: never; header?: never; - path: { - eventId: string; - }; + path?: never; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["ChangePasswordRequest"]; + }; + }; responses: { - /** @description OK */ + /** @description Password changed; all sessions revoked */ 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["BoardEvent"]; + "application/json": components["schemas"]["MessageResponse"]; + }; + }; + /** @description Current password incorrect or new password rejected */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; + /** @description Not signed in */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -2821,18 +5518,18 @@ export interface operations { }; }; }; - deleteEvent: { + requestPasswordReset: { parameters: { - query?: never; - header?: never; - path: { - eventId: string; + query: { + email: string; }; + header?: never; + path?: never; cookie?: never; }; requestBody?: never; responses: { - /** @description OK */ + /** @description Request accepted */ 200: { headers: { [name: string]: unknown; @@ -2841,6 +5538,15 @@ export interface operations { "application/json": components["schemas"]["MessageResponse"]; }; }; + /** @description Email missing */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; + }; + }; /** @description Request failed; body carries a human-readable message */ default: { headers: { @@ -2852,28 +5558,26 @@ export interface operations { }; }; }; - updateEvent: { + logout: { parameters: { query?: never; header?: never; - path: { - eventId: string; - }; + path?: never; cookie?: never; }; - requestBody: { + requestBody?: { content: { - "application/json": components["schemas"]["UpdateCalendarEventRequest"]; + "application/json": components["schemas"]["TokenRefreshRequest"]; }; }; responses: { - /** @description OK */ + /** @description Session ended */ 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["BoardEvent"]; + "application/json": components["schemas"]["MessageResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -2887,7 +5591,7 @@ export interface operations { }; }; }; - getAllPosters: { + logoutAllDevices: { parameters: { query?: never; header?: never; @@ -2896,13 +5600,22 @@ export interface operations { }; requestBody?: never; responses: { - /** @description OK */ + /** @description All sessions ended */ 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["Poster"][]; + "application/json": components["schemas"]["MessageResponse"]; + }; + }; + /** @description Not signed in */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -2916,26 +5629,35 @@ export interface operations { }; }; }; - createPoster: { + refreshToken: { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - requestBody?: { + requestBody: { content: { - "multipart/form-data": components["schemas"]["CreatePosterRequest"]; + "application/json": components["schemas"]["TokenRefreshRequest"]; }; }; responses: { - /** @description OK */ + /** @description New access and refresh tokens issued */ 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["Poster"]; + "application/json": components["schemas"]["TokenRefreshResponse"]; + }; + }; + /** @description Refresh token unknown, expired or already revoked */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -2949,24 +5671,35 @@ export interface operations { }; }; }; - getPostersForAudience: { + resetPassword: { parameters: { - query: { - audience: "brothers" | "sisters" | "both"; - }; + query?: never; header?: never; path?: never; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["ResetPasswordRequest"]; + }; + }; responses: { - /** @description OK */ + /** @description Password reset */ 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["Poster"][]; + "application/json": components["schemas"]["MessageResponse"]; + }; + }; + /** @description Token or password missing, invalid or expired */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -2980,28 +5713,30 @@ export interface operations { }; }; }; - getPosterById: { + signIn: { parameters: { query?: never; header?: never; - path: { - posterId: string; - }; + path?: never; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["LoginRequest"]; + }; + }; responses: { - /** @description OK */ + /** @description Authenticated; access and refresh tokens issued */ 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["Poster"]; + "application/json": components["schemas"]["JwtResponse"]; }; }; - /** @description Request failed; body carries a human-readable message */ - default: { + /** @description Bad credentials or disabled account */ + 401: { headers: { [name: string]: unknown; }; @@ -3009,21 +5744,8 @@ export interface operations { "application/json": components["schemas"]["MessageResponse"]; }; }; - }; - }; - deletePoster: { - parameters: { - query?: never; - header?: never; - path: { - posterId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description OK */ - 200: { + /** @description Too many failed attempts; account temporarily locked */ + 429: { headers: { [name: string]: unknown; }; @@ -3042,28 +5764,35 @@ export interface operations { }; }; }; - updatePoster: { + signUp: { parameters: { query?: never; header?: never; - path: { - posterId: string; - }; + path?: never; cookie?: never; }; requestBody: { content: { - "application/json": components["schemas"]["UpdatePosterRequest"]; + "application/json": components["schemas"]["SignupRequest"]; }; }; responses: { - /** @description OK */ + /** @description Account created; verification email sent */ 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["Poster"]; + "application/json": components["schemas"]["MessageResponse"]; + }; + }; + /** @description Validation failed or email already registered */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -3077,31 +5806,35 @@ export interface operations { }; }; }; - updatePosterImage: { + validateResetToken: { parameters: { query?: never; header?: never; - path: { - posterId: string; - }; + path?: never; cookie?: never; }; - requestBody?: { + requestBody: { content: { - "multipart/form-data": { - /** Format: binary */ - image: string; - }; + "application/json": components["schemas"]["ValidateResetTokenRequest"]; }; }; responses: { - /** @description OK */ + /** @description Token is valid */ 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["Poster"]; + "application/json": components["schemas"]["MessageResponse"]; + }; + }; + /** @description Token missing, invalid or expired */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -3115,7 +5848,7 @@ export interface operations { }; }; }; - refreshSignboard: { + validateToken: { parameters: { query?: never; header?: never; @@ -3124,8 +5857,17 @@ export interface operations { }; requestBody?: never; responses: { - /** @description OK */ + /** @description Token valid; user returned */ 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TokenValidationResponse"]; + }; + }; + /** @description Token missing, expired, or user no longer exists */ + 401: { headers: { [name: string]: unknown; }; @@ -3144,22 +5886,35 @@ export interface operations { }; }; }; - getAllSocials: { + verifyEmail: { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["VerifyEmailRequest"]; + }; + }; responses: { - /** @description OK */ + /** @description Email verified */ 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["PromotableSocialMedia"][]; + "application/json": components["schemas"]["MessageResponse"]; + }; + }; + /** @description Missing, invalid or expired token */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -3173,18 +5928,18 @@ export interface operations { }; }; }; - createSocial: { + getMinbarEvents: { parameters: { - query?: never; + query: { + audience: "brothers" | "sisters" | "both"; + year: number; + month: number; + }; header?: never; path?: never; cookie?: never; }; - requestBody: { - content: { - "application/json": components["schemas"]["CreatePromotableSocialMediaRequest"]; - }; - }; + requestBody?: never; responses: { /** @description OK */ 200: { @@ -3192,7 +5947,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["PromotableSocialMedia"]; + "application/json": components["schemas"]["EventView"][]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -3206,7 +5961,7 @@ export interface operations { }; }; }; - getSocialsForAudience: { + getMinbarPrayerSpaces: { parameters: { query: { audience: "brothers" | "sisters" | "both"; @@ -3223,7 +5978,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["PromotableSocialMedia"][]; + "application/json": components["schemas"]["PrayerSpaceView"][]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -3237,12 +5992,12 @@ export interface operations { }; }; }; - getSocialById: { + getMinbarPrayerSpaceById: { parameters: { query?: never; header?: never; path: { - socialId: string; + id: string; }; cookie?: never; }; @@ -3254,7 +6009,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["PromotableSocialMedia"]; + "application/json": components["schemas"]["PrayerSpaceView"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -3268,13 +6023,13 @@ export interface operations { }; }; }; - deleteSocial: { + getPublicBoardConfig: { parameters: { - query?: never; - header?: never; - path: { - socialId: string; + query: { + deviceId: string; }; + header?: never; + path?: never; cookie?: never; }; requestBody?: never; @@ -3285,7 +6040,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["MessageResponse"]; + "application/json": components["schemas"]["DeviceConfig"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -3299,20 +6054,16 @@ export interface operations { }; }; }; - updateSocial: { + getUpcomingEvents: { parameters: { - query?: never; - header?: never; - path: { - socialId: string; + query: { + audience: "brothers" | "sisters" | "both"; }; + header?: never; + path?: never; cookie?: never; }; - requestBody: { - content: { - "application/json": components["schemas"]["UpdatePromotableSocialMediaRequest"]; - }; - }; + requestBody?: never; responses: { /** @description OK */ 200: { @@ -3320,7 +6071,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["PromotableSocialMedia"]; + "application/json": components["schemas"]["BoardEvent"][]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -3334,9 +6085,13 @@ export interface operations { }; }; }; - getAllWeeklyContent: { + getEventsInRange: { parameters: { - query?: never; + query: { + audience: "brothers" | "sisters" | "both"; + start: string; + end: string; + }; header?: never; path?: never; cookie?: never; @@ -3349,7 +6104,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["WeeklyContent"][]; + "application/json": components["schemas"]["BoardEvent"][]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -3363,13 +6118,13 @@ export interface operations { }; }; }; - getWeeklyContentByYear: { + getBoardPayload: { parameters: { - query?: never; - header?: never; - path: { - year: number; + query: { + deviceId: string; }; + header?: never; + path?: never; cookie?: never; }; requestBody?: never; @@ -3380,7 +6135,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["WeeklyContent"][]; + "application/json": components["schemas"]["MusallahBoardPayload"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -3394,14 +6149,11 @@ export interface operations { }; }; }; - getAdminWeeklyContent: { + getCurrentWeeklyContent: { parameters: { query?: never; header?: never; - path: { - year: number; - weekNumber: number; - }; + path?: never; cookie?: never; }; requestBody?: never; @@ -3426,7 +6178,7 @@ export interface operations { }; }; }; - saveWeeklyContent: { + getPublicWeeklyContent: { parameters: { query?: never; header?: never; @@ -3436,11 +6188,7 @@ export interface operations { }; cookie?: never; }; - requestBody: { - content: { - "application/json": components["schemas"]["WeeklyContentRequest"]; - }; - }; + requestBody?: never; responses: { /** @description OK */ 200: { @@ -3462,13 +6210,12 @@ export interface operations { }; }; }; - deleteWeeklyContent: { + getJob: { parameters: { query?: never; header?: never; path: { - year: number; - weekNumber: number; + jobId: string; }; cookie?: never; }; @@ -3480,7 +6227,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["MessageResponse"]; + "application/json": components["schemas"]["EmailJobStatus"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -3494,12 +6241,10 @@ export interface operations { }; }; }; - createMediaEvent: { + getAllEvents: { parameters: { query: { - eventName: string; - eventDate: string; - status?: "UPCOMING" | "ONGOING" | "PAST"; + pageable: components["schemas"]["Pageable"]; }; header?: never; path?: never; @@ -3513,7 +6258,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["MessageResponse"]; + "application/json": components["schemas"]["PageEventResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -3527,14 +6272,18 @@ export interface operations { }; }; }; - getAllEventsForAdmin: { + createEvent: { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["CreateEventRequest"]; + }; + }; responses: { /** @description OK */ 200: { @@ -3542,7 +6291,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["MediaEvent"][]; + "application/json": components["schemas"]["EventResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -3556,16 +6305,18 @@ export interface operations { }; }; }; - featureUpload: { + createFullEvent: { parameters: { query?: never; header?: never; - path: { - uploadId: string; - }; + path?: never; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["CreateFullEventRequest"]; + }; + }; responses: { /** @description OK */ 200: { @@ -3573,7 +6324,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["MessageResponse"]; + "application/json": components["schemas"]["FullEventResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -3587,11 +6338,13 @@ export interface operations { }; }; }; - getAvailablePermissions: { + getEventById: { parameters: { query?: never; header?: never; - path?: never; + path: { + id: string; + }; cookie?: never; }; requestBody?: never; @@ -3602,7 +6355,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["PermissionResponse"][]; + "application/json": components["schemas"]["EventResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -3616,14 +6369,20 @@ export interface operations { }; }; }; - getAvailableRoles: { + updateEvent: { parameters: { query?: never; header?: never; - path?: never; + path: { + id: string; + }; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["UpdateEventRequest"]; + }; + }; responses: { /** @description OK */ 200: { @@ -3631,7 +6390,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["RoleDefinitionResponse"][]; + "application/json": components["schemas"]["EventResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -3645,12 +6404,12 @@ export interface operations { }; }; }; - approveUpload: { + deleteEvent: { parameters: { query?: never; header?: never; path: { - uploadId: string; + id: string; }; cookie?: never; }; @@ -3661,9 +6420,7 @@ export interface operations { headers: { [name: string]: unknown; }; - content: { - "application/json": components["schemas"]["MessageResponse"]; - }; + content?: never; }; /** @description Request failed; body carries a human-readable message */ default: { @@ -3676,16 +6433,24 @@ export interface operations { }; }; }; - deleteUpload: { + importAttendees: { parameters: { query?: never; header?: never; path: { - uploadId: string; + id: string; }; cookie?: never; }; - requestBody?: never; + requestBody?: { + content: { + "multipart/form-data": { + config: components["schemas"]["ImportConfig"]; + /** Format: binary */ + file: string; + }; + }; + }; responses: { /** @description OK */ 200: { @@ -3693,7 +6458,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["MessageResponse"]; + "application/json": components["schemas"]["ImportResult"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -3707,12 +6472,12 @@ export interface operations { }; }; }; - unapproveUpload: { + getTicketTypesByEvent: { parameters: { query?: never; header?: never; path: { - uploadId: string; + id: string; }; cookie?: never; }; @@ -3724,7 +6489,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["MessageResponse"]; + "application/json": components["schemas"]["TicketTypeResponse"][]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -3738,16 +6503,20 @@ export interface operations { }; }; }; - unfeatureUpload: { + createTicketType: { parameters: { query?: never; header?: never; path: { - uploadId: string; + id: string; }; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["CreateTicketTypeRequest"]; + }; + }; responses: { /** @description OK */ 200: { @@ -3755,7 +6524,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["MessageResponse"]; + "application/json": components["schemas"]["TicketTypeResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -3769,18 +6538,15 @@ export interface operations { }; }; }; - getAdminUploads: { + getTicketsByEvent: { parameters: { - query?: { - /** @description Zero-based page index (0..N) */ - page?: number; - /** @description The size of the page to be returned */ - size?: number; - /** @description Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. */ - sort?: string[]; + query: { + pageable: components["schemas"]["Pageable"]; }; header?: never; - path?: never; + path: { + id: string; + }; cookie?: never; }; requestBody?: never; @@ -3791,7 +6557,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["PageAdminUploadDto"]; + "application/json": components["schemas"]["PageTicketResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -3805,18 +6571,13 @@ export interface operations { }; }; }; - getApprovedUploads: { + resendAllTickets: { parameters: { - query?: { - /** @description Zero-based page index (0..N) */ - page?: number; - /** @description The size of the page to be returned */ - size?: number; - /** @description Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. */ - sort?: string[]; - }; + query?: never; header?: never; - path?: never; + path: { + id: string; + }; cookie?: never; }; requestBody?: never; @@ -3827,7 +6588,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["PageAdminUploadDto"]; + "application/json": components["schemas"]["EmailJobAccepted"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -3841,18 +6602,13 @@ export interface operations { }; }; }; - getFeaturedUploads: { + sendMissingTickets: { parameters: { - query?: { - /** @description Zero-based page index (0..N) */ - page?: number; - /** @description The size of the page to be returned */ - size?: number; - /** @description Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. */ - sort?: string[]; + query?: never; + header?: never; + path: { + id: string; }; - header?: never; - path?: never; cookie?: never; }; requestBody?: never; @@ -3863,7 +6619,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["PageAdminUploadDto"]; + "application/json": components["schemas"]["EmailJobAccepted"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -3877,18 +6633,13 @@ export interface operations { }; }; }; - getPendingUploads: { + getZonesByEvent: { parameters: { - query?: { - /** @description Zero-based page index (0..N) */ - page?: number; - /** @description The size of the page to be returned */ - size?: number; - /** @description Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. */ - sort?: string[]; - }; + query?: never; header?: never; - path?: never; + path: { + id: string; + }; cookie?: never; }; requestBody?: never; @@ -3899,7 +6650,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["PageAdminUploadDto"]; + "application/json": components["schemas"]["ZoneResponse"][]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -3913,16 +6664,18 @@ export interface operations { }; }; }; - createUser: { + addZoneToEvent: { parameters: { query?: never; header?: never; - path?: never; + path: { + id: string; + }; cookie?: never; }; requestBody: { content: { - "application/json": components["schemas"]["CreateUserRequest"]; + "application/json": components["schemas"]["AddZoneRequest"]; }; }; responses: { @@ -3932,7 +6685,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["MessageResponse"]; + "application/json": components["schemas"]["ZoneResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -3946,18 +6699,17 @@ export interface operations { }; }; }; - verifyUser: { + getOrders: { parameters: { - query?: never; + query?: { + eventId?: string; + externalRef?: string; + }; header?: never; path?: never; cookie?: never; }; - requestBody: { - content: { - "application/json": components["schemas"]["VerifyUserRequest"]; - }; - }; + requestBody?: never; responses: { /** @description OK */ 200: { @@ -3965,7 +6717,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["MessageResponse"]; + "application/json": components["schemas"]["OrderResponse"][]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -3979,18 +6731,16 @@ export interface operations { }; }; }; - addRoleToUser: { + createOrder_1: { parameters: { query?: never; header?: never; - path: { - userId: string; - }; + path?: never; cookie?: never; }; requestBody: { content: { - "application/json": "USER" | "ADMIN" | "BOARD_VIEWER" | "BOARD_EDITOR" | "BOARD_ADMIN" | "ROOT"; + "application/json": components["schemas"]["CreateOrderRequest"]; }; }; responses: { @@ -4000,7 +6750,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["MessageResponse"]; + "application/json": components["schemas"]["OrderResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -4014,20 +6764,16 @@ export interface operations { }; }; }; - grantUserPermission: { + getOrder_1: { parameters: { query?: never; header?: never; path: { - userId: string; + id: string; }; cookie?: never; }; - requestBody: { - content: { - "application/json": "MEDIA_UPLOAD_SELF" | "MEDIA_UPLOAD_MODERATE" | "MEDIA_UPLOAD_READ" | "MEDIA_EVENT_WRITE" | "BOARD_CONTENT_READ" | "BOARD_POSTER_WRITE" | "BOARD_EVENT_WRITE" | "BOARD_SOCIAL_WRITE" | "BOARD_WEEKLY_WRITE" | "BOARD_TICKER_WRITE" | "BOARD_CONFIG_READ" | "BOARD_CONFIG_WRITE" | "BOARD_REFRESH" | "BOARD_DEVICE_READ" | "BOARD_DEVICE_ENROLL" | "BOARD_DEVICE_REVOKE" | "BOARD_TELEMETRY_SUBSCRIBE" | "BOARD_COMMAND_BENIGN" | "BOARD_COMMAND_DISRUPTIVE" | "BOARD_COMMAND_INSPECT" | "IAM_USER_READ" | "IAM_USER_WRITE" | "IAM_ROLE_GRANT" | "AUDIT_READ"; - }; - }; + requestBody?: never; responses: { /** @description OK */ 200: { @@ -4035,7 +6781,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["MessageResponse"]; + "application/json": components["schemas"]["OrderResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -4049,20 +6795,16 @@ export interface operations { }; }; }; - removeRoleFromUser: { + cancelOrder_1: { parameters: { query?: never; header?: never; path: { - userId: string; + id: string; }; cookie?: never; }; - requestBody: { - content: { - "application/json": "USER" | "ADMIN" | "BOARD_VIEWER" | "BOARD_EDITOR" | "BOARD_ADMIN" | "ROOT"; - }; - }; + requestBody?: never; responses: { /** @description OK */ 200: { @@ -4070,7 +6812,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["MessageResponse"]; + "application/json": components["schemas"]["OrderResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -4084,20 +6826,16 @@ export interface operations { }; }; }; - revokeUserPermission: { + confirmManualPayment: { parameters: { query?: never; header?: never; path: { - userId: string; + id: string; }; cookie?: never; }; - requestBody: { - content: { - "application/json": "MEDIA_UPLOAD_SELF" | "MEDIA_UPLOAD_MODERATE" | "MEDIA_UPLOAD_READ" | "MEDIA_EVENT_WRITE" | "BOARD_CONTENT_READ" | "BOARD_POSTER_WRITE" | "BOARD_EVENT_WRITE" | "BOARD_SOCIAL_WRITE" | "BOARD_WEEKLY_WRITE" | "BOARD_TICKER_WRITE" | "BOARD_CONFIG_READ" | "BOARD_CONFIG_WRITE" | "BOARD_REFRESH" | "BOARD_DEVICE_READ" | "BOARD_DEVICE_ENROLL" | "BOARD_DEVICE_REVOKE" | "BOARD_TELEMETRY_SUBSCRIBE" | "BOARD_COMMAND_BENIGN" | "BOARD_COMMAND_DISRUPTIVE" | "BOARD_COMMAND_INSPECT" | "IAM_USER_READ" | "IAM_USER_WRITE" | "IAM_ROLE_GRANT" | "AUDIT_READ"; - }; - }; + requestBody?: never; responses: { /** @description OK */ 200: { @@ -4105,7 +6843,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["MessageResponse"]; + "application/json": components["schemas"]["OrderResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -4119,18 +6857,13 @@ export interface operations { }; }; }; - getAllUsers: { + completeMockPayment: { parameters: { - query?: { - /** @description Zero-based page index (0..N) */ - page?: number; - /** @description The size of the page to be returned */ - size?: number; - /** @description Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. */ - sort?: string[]; - }; + query?: never; header?: never; - path?: never; + path: { + orderId: string; + }; cookie?: never; }; requestBody?: never; @@ -4141,7 +6874,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["PageUserInfoResponse"]; + "application/json": components["schemas"]["OrderResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -4155,44 +6888,28 @@ export interface operations { }; }; }; - enrollAgent: { + webhook: { parameters: { query?: never; header?: never; - path?: never; + path: { + providerId: string; + }; cookie?: never; }; - requestBody: { + requestBody?: { content: { - "application/json": components["schemas"]["AgentEnrollRequest"]; + "application/json": string; }; }; responses: { - /** @description Device enrolled; identity and websocket URL returned */ + /** @description OK */ 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["AgentEnrollResponse"]; - }; - }; - /** @description Public key malformed or unsupported */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["MessageResponse"]; - }; - }; - /** @description Enrollment token invalid, expired, or already used */ - 401: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["MessageResponse"]; + "application/json": string; }; }; /** @description Request failed; body carries a human-readable message */ @@ -4206,30 +6923,29 @@ export interface operations { }; }; }; - changePassword: { + getTicketZoneEntryCount: { parameters: { query?: never; header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["ChangePasswordRequest"]; + path: { + ticketId: string; + zoneId: string; }; + cookie?: never; }; + requestBody?: never; responses: { - /** @description Password changed; all sessions revoked */ + /** @description OK */ 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["MessageResponse"]; + "application/json": number; }; }; - /** @description Current password incorrect or new password rejected */ - 400: { + /** @description Request failed; body carries a human-readable message */ + default: { headers: { [name: string]: unknown; }; @@ -4237,13 +6953,28 @@ export interface operations { "application/json": components["schemas"]["MessageResponse"]; }; }; - /** @description Not signed in */ - 403: { + }; + }; + getScanHistoryForEvent: { + parameters: { + query: { + pageable: components["schemas"]["Pageable"]; + }; + header?: never; + path: { + eventId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["MessageResponse"]; + "application/json": components["schemas"]["PageScanEventResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -4257,33 +6988,26 @@ export interface operations { }; }; }; - requestPasswordReset: { + scanTicket: { parameters: { - query: { - email: string; - }; + query?: never; header?: never; path?: never; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["ScanRequest"]; + }; + }; responses: { - /** @description Request accepted */ + /** @description OK */ 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["MessageResponse"]; - }; - }; - /** @description Email missing */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["MessageResponse"]; + "application/json": components["schemas"]["ScanResult"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -4297,26 +7021,26 @@ export interface operations { }; }; }; - logout: { + scanByQr: { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - requestBody?: { + requestBody: { content: { - "application/json": components["schemas"]["TokenRefreshRequest"]; + "application/json": components["schemas"]["QrScanRequest"]; }; }; responses: { - /** @description Session ended */ + /** @description OK */ 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["MessageResponse"]; + "application/json": components["schemas"]["ScanResult"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -4330,31 +7054,24 @@ export interface operations { }; }; }; - logoutAllDevices: { + getScanHistoryForTicket: { parameters: { query?: never; header?: never; - path?: never; + path: { + ticketId: string; + }; cookie?: never; }; requestBody?: never; responses: { - /** @description All sessions ended */ + /** @description OK */ 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["MessageResponse"]; - }; - }; - /** @description Not signed in */ - 403: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["MessageResponse"]; + "application/json": components["schemas"]["ScanEventResponse"][]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -4368,7 +7085,7 @@ export interface operations { }; }; }; - refreshToken: { + validateTicketForZone: { parameters: { query?: never; header?: never; @@ -4377,26 +7094,17 @@ export interface operations { }; requestBody: { content: { - "application/json": components["schemas"]["TokenRefreshRequest"]; + "application/json": components["schemas"]["ValidateRequest"]; }; }; responses: { - /** @description New access and refresh tokens issued */ + /** @description OK */ 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["TokenRefreshResponse"]; - }; - }; - /** @description Refresh token unknown, expired or already revoked */ - 401: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["MessageResponse"]; + "application/json": components["schemas"]["ValidationResult"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -4410,35 +7118,26 @@ export interface operations { }; }; }; - resetPassword: { + getScanHistoryForZone: { parameters: { - query?: never; + query: { + pageable: components["schemas"]["Pageable"]; + }; header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["ResetPasswordRequest"]; + path: { + zoneId: string; }; + cookie?: never; }; + requestBody?: never; responses: { - /** @description Password reset */ + /** @description OK */ 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["MessageResponse"]; - }; - }; - /** @description Token or password missing, invalid or expired */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["MessageResponse"]; + "application/json": components["schemas"]["PageScanEventResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -4452,30 +7151,28 @@ export interface operations { }; }; }; - signIn: { + getAllTicketTypes: { parameters: { - query?: never; + query: { + pageable: components["schemas"]["Pageable"]; + }; header?: never; path?: never; cookie?: never; }; - requestBody: { - content: { - "application/json": components["schemas"]["LoginRequest"]; - }; - }; + requestBody?: never; responses: { - /** @description Authenticated; access and refresh tokens issued */ + /** @description OK */ 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["JwtResponse"]; + "application/json": components["schemas"]["PageTicketTypeResponse"]; }; }; - /** @description Bad credentials or disabled account */ - 401: { + /** @description Request failed; body carries a human-readable message */ + default: { headers: { [name: string]: unknown; }; @@ -4483,13 +7180,26 @@ export interface operations { "application/json": components["schemas"]["MessageResponse"]; }; }; - /** @description Too many failed attempts; account temporarily locked */ - 429: { + }; + }; + getTicketTypeById: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["MessageResponse"]; + "application/json": components["schemas"]["TicketTypeResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -4503,30 +7213,32 @@ export interface operations { }; }; }; - signUp: { + updateTicketType: { parameters: { query?: never; header?: never; - path?: never; + path: { + id: string; + }; cookie?: never; }; requestBody: { content: { - "application/json": components["schemas"]["SignupRequest"]; + "application/json": components["schemas"]["UpdateTicketTypeRequest"]; }; }; responses: { - /** @description Account created; verification email sent */ + /** @description OK */ 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["MessageResponse"]; + "application/json": components["schemas"]["TicketTypeResponse"]; }; }; - /** @description Validation failed or email already registered */ - 400: { + /** @description Request failed; body carries a human-readable message */ + default: { headers: { [name: string]: unknown; }; @@ -4534,6 +7246,26 @@ export interface operations { "application/json": components["schemas"]["MessageResponse"]; }; }; + }; + }; + deleteTicketType: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; /** @description Request failed; body carries a human-readable message */ default: { headers: { @@ -4545,7 +7277,7 @@ export interface operations { }; }; }; - validateResetToken: { + createTicket: { parameters: { query?: never; header?: never; @@ -4554,26 +7286,17 @@ export interface operations { }; requestBody: { content: { - "application/json": components["schemas"]["ValidateResetTokenRequest"]; + "application/json": components["schemas"]["CreateTicketRequest"]; }; }; responses: { - /** @description Token is valid */ + /** @description OK */ 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["MessageResponse"]; - }; - }; - /** @description Token missing, invalid or expired */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["MessageResponse"]; + "application/json": components["schemas"]["TicketResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -4587,31 +7310,24 @@ export interface operations { }; }; }; - validateToken: { + getTicketById: { parameters: { query?: never; header?: never; - path?: never; + path: { + id: string; + }; cookie?: never; }; requestBody?: never; responses: { - /** @description Token valid; user returned */ + /** @description OK */ 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["TokenValidationResponse"]; - }; - }; - /** @description Token missing, expired, or user no longer exists */ - 401: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["MessageResponse"]; + "application/json": components["schemas"]["TicketResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -4625,35 +7341,28 @@ export interface operations { }; }; }; - verifyEmail: { + updateTicket: { parameters: { query?: never; header?: never; - path?: never; + path: { + id: string; + }; cookie?: never; }; requestBody: { content: { - "application/json": components["schemas"]["VerifyEmailRequest"]; + "application/json": components["schemas"]["UpdateTicketRequest"]; }; }; responses: { - /** @description Email verified */ + /** @description OK */ 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["MessageResponse"]; - }; - }; - /** @description Missing, invalid or expired token */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["MessageResponse"]; + "application/json": components["schemas"]["TicketResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -4667,11 +7376,13 @@ export interface operations { }; }; }; - getPublicEvents: { + deleteTicket: { parameters: { query?: never; header?: never; - path?: never; + path: { + id: string; + }; cookie?: never; }; requestBody?: never; @@ -4681,9 +7392,7 @@ export interface operations { headers: { [name: string]: unknown; }; - content: { - "application/json": components["schemas"]["MediaEvent"][]; - }; + content?: never; }; /** @description Request failed; body carries a human-readable message */ default: { @@ -4696,7 +7405,7 @@ export interface operations { }; }; }; - getPublicEventById: { + resendTicket: { parameters: { query?: never; header?: never; @@ -4713,7 +7422,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["MediaEvent"]; + "application/json": components["schemas"]["EmailJobAccepted"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -4727,15 +7436,10 @@ export interface operations { }; }; }; - getGalleryUploads: { + getAllZones: { parameters: { - query?: { - /** @description Zero-based page index (0..N) */ - page?: number; - /** @description The size of the page to be returned */ - size?: number; - /** @description Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. */ - sort?: string[]; + query: { + pageable: components["schemas"]["Pageable"]; }; header?: never; path?: never; @@ -4749,7 +7453,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["PageGalleryItemDto"]; + "application/json": components["schemas"]["PageZoneResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -4763,19 +7467,12 @@ export interface operations { }; }; }; - getGalleryByEvent: { + getZoneById: { parameters: { - query?: { - /** @description Zero-based page index (0..N) */ - page?: number; - /** @description The size of the page to be returned */ - size?: number; - /** @description Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. */ - sort?: string[]; - }; + query?: never; header?: never; path: { - eventId: string; + id: string; }; cookie?: never; }; @@ -4787,7 +7484,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["PageGalleryItemDto"]; + "application/json": components["schemas"]["ZoneResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -4801,16 +7498,20 @@ export interface operations { }; }; }; - getPublicBoardConfig: { + updateZone: { parameters: { - query: { - deviceId: string; - }; + query?: never; header?: never; - path?: never; + path: { + id: string; + }; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["UpdateZoneRequest"]; + }; + }; responses: { /** @description OK */ 200: { @@ -4818,7 +7519,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["DeviceConfig"]; + "application/json": components["schemas"]["ZoneResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -4832,13 +7533,13 @@ export interface operations { }; }; }; - getUpcomingEvents: { + deleteZone: { parameters: { - query: { - audience: "brothers" | "sisters" | "both"; - }; + query?: never; header?: never; - path?: never; + path: { + id: string; + }; cookie?: never; }; requestBody?: never; @@ -4848,9 +7549,7 @@ export interface operations { headers: { [name: string]: unknown; }; - content: { - "application/json": components["schemas"]["BoardEvent"][]; - }; + content?: never; }; /** @description Request failed; body carries a human-readable message */ default: { @@ -4863,15 +7562,20 @@ export interface operations { }; }; }; - getEventsInRange: { + getUploadsByEvent: { parameters: { - query: { - audience: "brothers" | "sisters" | "both"; - start: string; - end: string; + query?: { + /** @description Zero-based page index (0..N) */ + page?: number; + /** @description The size of the page to be returned */ + size?: number; + /** @description Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. */ + sort?: string[]; }; header?: never; - path?: never; + path: { + eventId: string; + }; cookie?: never; }; requestBody?: never; @@ -4882,7 +7586,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["BoardEvent"][]; + "application/json": components["schemas"]["PageUploadDto"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -4896,11 +7600,9 @@ export interface operations { }; }; }; - getBoardPayload: { + getUploadLimits: { parameters: { - query: { - deviceId: string; - }; + query?: never; header?: never; path?: never; cookie?: never; @@ -4913,7 +7615,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["MusallahBoardPayload"]; + "application/json": components["schemas"]["UploadLimitsResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -4927,11 +7629,22 @@ export interface operations { }; }; }; - getCurrentWeeklyContent: { + completeDirectUpload: { parameters: { - query?: never; + query: { + objectKey: string; + filename: string; + contentType: string; + fileSize: number; + instagramHandle?: string; + description?: string; + anon?: boolean; + expectedSha256: string; + }; header?: never; - path?: never; + path: { + eventId: string; + }; cookie?: never; }; requestBody?: never; @@ -4942,7 +7655,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["WeeklyContent"]; + "application/json": components["schemas"]["UploadCompletionResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -4956,13 +7669,17 @@ export interface operations { }; }; }; - getPublicWeeklyContent: { + generatePresignedUploadUrl: { parameters: { - query?: never; + query: { + filename: string; + contentType: string; + fileSize: number; + expectedSha256: string; + }; header?: never; path: { - year: number; - weekNumber: number; + eventId: string; }; cookie?: never; }; @@ -4974,7 +7691,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["WeeklyContent"]; + "application/json": components["schemas"]["PresignedUploadResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -4988,19 +7705,12 @@ export interface operations { }; }; }; - getUploadsByEvent: { + getUploadById: { parameters: { - query?: { - /** @description Zero-based page index (0..N) */ - page?: number; - /** @description The size of the page to be returned */ - size?: number; - /** @description Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. */ - sort?: string[]; - }; + query?: never; header?: never; path: { - eventId: string; + uploadId: string; }; cookie?: never; }; @@ -5012,7 +7722,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["PageUploadDto"]; + "application/json": components["schemas"]["UploadDto"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -5026,7 +7736,7 @@ export interface operations { }; }; }; - getUploadLimits: { + getUserOrders: { parameters: { query?: never; header?: never; @@ -5041,7 +7751,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["UploadLimitsResponse"]; + "application/json": components["schemas"]["MinbarOrderResponse"][]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -5055,25 +7765,18 @@ export interface operations { }; }; }; - completeDirectUpload: { + createOrder: { parameters: { - query: { - objectKey: string; - filename: string; - contentType: string; - fileSize: number; - instagramHandle?: string; - description?: string; - anon?: boolean; - expectedSha256: string; - }; + query?: never; header?: never; - path: { - eventId: string; - }; + path?: never; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["MinbarCreateOrderRequest"]; + }; + }; responses: { /** @description OK */ 200: { @@ -5081,7 +7784,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["UploadCompletionResponse"]; + "application/json": components["schemas"]["MinbarOrderResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -5095,17 +7798,12 @@ export interface operations { }; }; }; - generatePresignedUploadUrl: { + getOrder: { parameters: { - query: { - filename: string; - contentType: string; - fileSize: number; - expectedSha256: string; - }; + query?: never; header?: never; path: { - eventId: string; + id: string; }; cookie?: never; }; @@ -5117,7 +7815,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["PresignedUploadResponse"]; + "application/json": components["schemas"]["MinbarOrderResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -5131,12 +7829,12 @@ export interface operations { }; }; }; - getUploadById: { + cancelOrder: { parameters: { query?: never; header?: never; path: { - uploadId: string; + id: string; }; cookie?: never; }; @@ -5148,7 +7846,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["UploadDto"]; + "application/json": components["schemas"]["MinbarOrderResponse"]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -5224,7 +7922,7 @@ export interface operations { }; }; }; - getUserStats: { + getUserTickets: { parameters: { query?: never; header?: never; @@ -5239,7 +7937,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["UserStatsResponse"]; + "application/json": components["schemas"]["BoardingPassView"][]; }; }; /** @description Request failed; body carries a human-readable message */ @@ -5275,7 +7973,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["PageGalleryItemDto"]; + "application/json": components["schemas"]["PageUploadDto"]; }; }; /** @description Request failed; body carries a human-readable message */ diff --git a/src/assets/Cookie Monster GIF - Cookie Monster - Discover & Share GIFs.gif b/src/assets/Cookie Monster GIF - Cookie Monster - Discover & Share GIFs.gif deleted file mode 100644 index e27d757..0000000 Binary files a/src/assets/Cookie Monster GIF - Cookie Monster - Discover & Share GIFs.gif and /dev/null differ diff --git a/src/assets/react.svg b/src/assets/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/components/CacheIndicator.jsx b/src/components/CacheIndicator.jsx deleted file mode 100644 index e69de29..0000000 diff --git a/src/components/Footer.jsx b/src/components/Footer.jsx deleted file mode 100644 index 1f5d48a..0000000 --- a/src/components/Footer.jsx +++ /dev/null @@ -1,103 +0,0 @@ -import { Heart, Github, Code, ExternalLink } from 'lucide-react'; - -function Footer() { - return ( -
-
- {/* Main Footer Content */} -
- {/* Brand Section */} -
-
-
-
-
- LensBridge -
-
- - LensBridge 2027 - -
-

- Share your UTM MSA memories and connect with the community through photos and videos. -

-
- - {/* Quick Links */} - - - {/* Open Source & Contact */} -
-

Open Source

-
-
- - MIT Licensed -
- - - View on GitHub - - - -
-
-
- - {/* Footer Bottom */} -
-
- {/* Made with Love */} -
- Made with - - for UTM MSA by - - IbraSoft - -
- - {/* Copyright */} -
- © {new Date().getFullYear()} LensBridge. All rights reserved. -
-
-
-
-
- ); -} - -export default Footer; diff --git a/src/components/Header.jsx b/src/components/Header.jsx deleted file mode 100644 index 8701164..0000000 --- a/src/components/Header.jsx +++ /dev/null @@ -1,348 +0,0 @@ -import { Link, useLocation, useNavigate } from 'react-router-dom'; -import { Camera, Home, Upload, Grid3x3, Menu, X, Sparkles, User, LogOut, Shield, Crown } from 'lucide-react'; -import { useState } from 'react'; -import { useAuth } from '../context/AuthContext'; -import { ADMIN_SECTION_PERMISSIONS, BOARD_SECTION_PERMISSIONS } from '../utils/permissions'; - -function Header() { - const location = useLocation(); - const navigate = useNavigate(); - const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); - const { user, logout, isAdmin, isRoot, isLoading, canAny } = useAuth(); - - /** - * Where the shield goes. - * - * A BOARD_EDITOR holds no media permission, so `isAdmin()` is false for them - * and they used to get no link at all — while /admin/board, the page they - * exist to use, sat one URL away. Sending them to /admin instead would be - * worse: PermissionRoute refuses it. So the destination follows the - * permissions, and the link disappears only when neither section is reachable. - */ - const adminLink = canAny(ADMIN_SECTION_PERMISSIONS) - ? { href: '/admin', label: 'Admin', mobileLabel: 'Admin Dashboard' } - : canAny(BOARD_SECTION_PERMISSIONS) - ? { href: '/admin/board', label: 'Boards', mobileLabel: 'Board Management' } - : null; - - /** - * How the account presents itself in the profile block. - * - * ROOT gets its own tier rather than sharing the admin badge. It is the one - * account that can grant roles, and an operator glancing at the header should - * be able to tell they are signed in as it — the same treatment as a media - * moderator invites exactly the "I thought I was on my normal account" mistake. - * - * Purely cosmetic; nothing here gates anything. - */ - const identity = isRoot() - ? { - badge: 'ROOT', - Icon: Crown, - gradient: 'from-red-600 to-orange-500', - tint: 'bg-red-50 hover:bg-red-100', - } - : isAdmin() - ? { - badge: 'Admin', - Icon: Shield, - gradient: 'from-purple-600 to-blue-600', - tint: 'bg-purple-50 hover:bg-purple-100', - } - : { - badge: null, - Icon: User, - gradient: 'from-blue-600 to-green-600', - tint: 'bg-blue-50 hover:bg-blue-100', - }; - - const handleLogout = async () => { - try { - await logout(); - navigate('/'); - } catch (error) { - console.error('Logout error:', error); - // Even if logout fails, navigate to home - navigate('/'); - } finally { - setIsMobileMenuOpen(false); - } - }; - - const isActive = (path) => { - return location.pathname === path; - }; - - const navigation = [ - { name: 'Home', href: '/', icon: Home }, - { name: 'Upload', href: '/upload', icon: Upload }, - { name: 'Gallery', href: '/gallery', icon: Grid3x3 }, - ]; - - return ( -
-
-
- {/* Logo */} -
- -
-
-
- LensBridge -
-
-
- - LensBridge - - 2027 -
- -
- - {/* Desktop Navigation - Absolutely Centered */} - - - {/* Spacer to push CTA to right */} -
- - {/* CTA Button - Right side */} -
- {user ? ( - <> - {/* Admin link, pointed at whichever console section is reachable */} - {adminLink && ( - - - {adminLink.label} - {isActive(adminLink.href) && ( -
- )} - - )} - - {/* User Info */} -
- -

- {user.firstName} {user.lastName} - {identity.badge && ( - - {identity.badge} - - )} -

-

{user.email}

- - -
-
- -
- -
- {/* Logout Button */} - - - ) : ( - <> - {isLoading ? ( -
-
- Signing in... -
- ) : ( - - - Sign In - - )} - - - Share Now - - - )} -
- - {/* Mobile menu button */} -
- -
-
- - {/* Mobile Navigation */} - {isMobileMenuOpen && ( -
- -
- )} -
-
- ); -} - -export default Header; diff --git a/src/components/PermissionRoute.jsx b/src/components/PermissionRoute.jsx index 325755a..e1fa636 100644 --- a/src/components/PermissionRoute.jsx +++ b/src/components/PermissionRoute.jsx @@ -1,6 +1,7 @@ import { Navigate, useLocation, Link } from 'react-router-dom'; import { ShieldAlert } from 'lucide-react'; import { useAuth } from '../context/AuthContext'; +import { Button, Spinner } from './ui'; /** * Route guard that gates on permissions rather than a role name. @@ -8,75 +9,63 @@ import { useAuth } from '../context/AuthContext'; * Every board and admin route used to sit behind a single `isRoot()` check, * which is why "fix a typo on the sisters' board" and "reboot the hardware" * needed the same grant. Routes now ask for the narrowest permission that makes - * the page useful at all — usually a read permission — and the controls inside - * gate themselves individually. + * the page useful at all — usually a read — and the controls inside gate + * themselves individually. * * `anyOf` is the normal case: a page is worth opening if the user can do any one * of the things on it. `allOf` exists for the rare page where a partial view * would be misleading. * - * The denial screen names the permission the user is missing, because "Access - * Denied" with no further detail turns into a message to whoever administers the - * console asking what to request. + * The denial screen names the permission that is missing. "Access denied" with + * no further detail turns into a message to whoever administers the console + * asking what, exactly, to request. */ -function PermissionRoute({ children, anyOf, allOf, label = 'this page' }) { +export default function PermissionRoute({ children, anyOf, allOf, label = 'this page' }) { const { isAuthenticated, isLoading, canAny, canAll } = useAuth(); const location = useLocation(); - if (isLoading) { - return ( -
-
-
-

Checking permissions...

-
-
- ); - } - - if (!isAuthenticated) { - return ; - } + if (isLoading) return ; + if (!isAuthenticated) return ; const required = allOf ?? anyOf ?? []; const allowed = allOf ? canAll(allOf) : canAny(anyOf ?? []); + if (allowed) return children; - if (!allowed) { - return ( -
-
-
- -
-

Not permitted

-

- Your account doesn't have access to {label}. -

-

- {allOf ? 'Requires all of:' : 'Requires one of:'} -

-
- {required.map((permission) => ( - - {permission} - - ))} -
- - Back to home - + return ( +
+
+
+
+

Not permitted

+

+ Your account does not have access to {label}. +

+

{allOf ? 'Requires all of' : 'Requires one of'}

+
+ {required.map((permission) => ( + + {permission} + + ))} +
+ + +
- ); - } - - return children; +
+ ); } -export default PermissionRoute; +/** Shared with ProtectedRoute so both guards look identical while resolving. */ +export function RouteSpinner({ label }) { + return ( +
+ +

{label}

+
+ ); +} diff --git a/src/components/ProtectedRoute.jsx b/src/components/ProtectedRoute.jsx index cd4f933..b17bd2c 100644 --- a/src/components/ProtectedRoute.jsx +++ b/src/components/ProtectedRoute.jsx @@ -1,27 +1,13 @@ import { Navigate, useLocation } from 'react-router-dom'; import { useAuth } from '../context/AuthContext'; +import { RouteSpinner } from './PermissionRoute'; -function ProtectedRoute({ children }) { +/** Signed-in check with no permission requirement. */ +export default function ProtectedRoute({ children }) { const { isAuthenticated, isLoading } = useAuth(); const location = useLocation(); - if (isLoading) { - return ( -
-
-
-

Checking authentication...

-
-
- ); - } - - if (!isAuthenticated) { - // Redirect to login page with the current location as state - return ; - } - + if (isLoading) return ; + if (!isAuthenticated) return ; return children; } - -export default ProtectedRoute; diff --git a/src/components/ScrollToTop.jsx b/src/components/ScrollToTop.jsx deleted file mode 100644 index 9f7e99e..0000000 --- a/src/components/ScrollToTop.jsx +++ /dev/null @@ -1,18 +0,0 @@ -import { useEffect } from 'react'; -import { useLocation } from 'react-router-dom'; - -function ScrollToTop() { - const { pathname } = useLocation(); - - useEffect(() => { - window.scrollTo({ - top: 0, - left: 0, - behavior: 'instant' // Using instant instead of smooth for immediate feedback - }); - }, [pathname]); - - return null; -} - -export default ScrollToTop; diff --git a/src/components/admin/AccessEditor.jsx b/src/components/admin/AccessEditor.jsx new file mode 100644 index 0000000..ba77fad --- /dev/null +++ b/src/components/admin/AccessEditor.jsx @@ -0,0 +1,310 @@ +import { useMemo, useState } from 'react'; +import { Lock, Minus, Plus, ShieldAlert } from 'lucide-react'; +import { Modal, Button, Badge, Tabs, ErrorNote, SearchInput, KeyValue } from '../ui'; +import { + DOMAIN_LABELS, + PERMISSION_DESCRIPTIONS, + PERMISSION_NAMES, + SENSITIVE_PERMISSIONS, +} from '../../utils/permissions'; +import { bareRoleName, canAssignRole } from './roles'; + +const DOMAIN_ORDER = Object.keys(DOMAIN_LABELS); +const domainRank = (d) => { + const i = DOMAIN_ORDER.indexOf(d); + return i === -1 ? DOMAIN_ORDER.length : i; +}; + +/** + * Everything one account is allowed to do, in one place. + * + * Roles and direct grants used to be two separate modals reached from two + * separate menu items, which meant the answer to "why does this person have + * telemetry access" lived in whichever one you had not opened. They are tabs + * here, over a shared header that states the effective total. + * + * The distinction the permissions tab exists to draw is **effective vs direct**. + * Someone holds `board:poster:write` either because BOARD_EDITOR carries it or + * because it was granted to them specifically, and only the second is revocable + * here. Without the split, an operator tries to revoke a role-derived permission + * and gets a server error that reads like a bug. + * + * Rows the current operator cannot act on still render when the target holds + * the permission — "this account has telemetry access and I cannot take it + * away" is information they need. What is hidden is the control, not the fact. + * The server enforces all of this regardless; every refusal it sends back is + * shown verbatim, because those messages name the rule that was broken. + */ +export default function AccessEditor({ + open, + onClose, + user, + roles, + permissions, + heldPermissions, + onSetRole, + onSetPermission, +}) { + const [tab, setTab] = useState('roles'); + const [query, setQuery] = useState(''); + const [pending, setPending] = useState(null); + const [outcome, setOutcome] = useState(null); + + const effective = useMemo( + () => new Set(user?.effectivePermissions ?? []), + [user?.effectivePermissions] + ); + const direct = useMemo(() => new Set(user?.directPermissions ?? []), [user?.directPermissions]); + const held = useMemo(() => new Set(heldPermissions), [heldPermissions]); + const userRoles = useMemo( + () => new Set((user?.roles ?? []).map(bareRoleName)), + [user?.roles] + ); + + const groups = useMemo(() => { + const needle = query.trim().toLowerCase(); + const byDomain = new Map(); + for (const permission of permissions ?? []) { + // Only permissions the target already holds, or the operator could hand + // over, are actionable or informative. The rest would be dead rows. + if (!effective.has(permission.authority) && !held.has(permission.authority)) continue; + if (needle && !permission.authority?.toLowerCase().includes(needle)) continue; + const domain = permission.domain ?? 'other'; + if (!byDomain.has(domain)) byDomain.set(domain, []); + byDomain.get(domain).push(permission); + } + return [...byDomain.entries()].sort(([a], [b]) => domainRank(a) - domainRank(b)); + }, [permissions, effective, held, query]); + + const roleDerived = effective.size - direct.size; + + const run = async (key, action) => { + setPending(key); + setOutcome(null); + const result = await action(); + setPending(null); + setOutcome(result); + }; + + if (!user) return null; + + const name = [user.firstName, user.lastName].filter(Boolean).join(' ') || user.email; + + return ( + + Done + + } + > +
+
+
Effective
+
{effective.size}
+
+
+
From roles
+
{Math.max(0, roleDerived)}
+
+
+
Direct
+
{direct.size}
+
+
+ + {outcome && ( +
+ {outcome.ok ? ( +
+ {outcome.message} +
+ ) : ( + {outcome.message} + )} +
+ )} + + + + {tab === 'roles' && ( +
+ {(roles ?? []).map((role) => { + const bare = bareRoleName(role.name); + const assigned = userRoles.has(bare); + const allowed = canAssignRole(role, heldPermissions); + const busy = pending === `role:${bare}`; + + return ( +
+
+
+ {bare} + {assigned && ( + + assigned + + )} + {bare === 'ROOT' && ( + + apex + + )} +
+

+ {role.description || 'No description on the server.'} +

+

+ {role.permissions?.length ?? 0} permissions + {!allowed && ' · includes permissions you do not hold'} +

+
+ + {allowed ? ( + + ) : ( + + + + )} +
+ ); + })} +
+ )} + + {tab === 'permissions' && ( + <> + + + {groups.length === 0 ? ( +

+ Nothing to show. You can only grant permissions you hold yourself, and this account + holds none that you do not. +

+ ) : ( +
+ {groups.map(([domain, list]) => ( +
+

{DOMAIN_LABELS[domain] ?? domain}

+
+ {list.map((permission) => { + const authority = permission.authority; + const isDirect = direct.has(authority); + const isEffective = effective.has(authority); + const canAct = held.has(authority); + const enumName = permission.name ?? PERMISSION_NAMES[authority]; + const busy = pending === `perm:${authority}`; + + return ( +
+
+
+ {authority} + {isDirect ? ( + + direct + + ) : isEffective ? ( + + via role + + ) : null} + {SENSITIVE_PERMISSIONS.has(authority) && ( + + sensitive + + )} +
+ {PERMISSION_DESCRIPTIONS[authority] && ( +

+ {PERMISSION_DESCRIPTIONS[authority]} +

+ )} +
+ + {isEffective && !isDirect ? ( + + + + ) : canAct ? ( + + ) : ( + + + + )} +
+ ); + })} +
+
+ ))} +
+ )} + + )} + +
+ + {user.id} + + + {user.email} + +
+
+ ); +} diff --git a/src/components/admin/AuditActionBadge.jsx b/src/components/admin/AuditActionBadge.jsx deleted file mode 100644 index e7d7425..0000000 --- a/src/components/admin/AuditActionBadge.jsx +++ /dev/null @@ -1,13 +0,0 @@ -import { auditActionTone, AUDIT_TONE_CLASSES } from './audit'; - -function AuditActionBadge({ action }) { - return ( - - {action} - - ); -} - -export default AuditActionBadge; diff --git a/src/components/admin/CreateUserModal.jsx b/src/components/admin/CreateUserModal.jsx index 8b1a2cc..76fc802 100644 --- a/src/components/admin/CreateUserModal.jsx +++ b/src/components/admin/CreateUserModal.jsx @@ -1,177 +1,183 @@ -import { useEffect, useRef, useState } from 'react'; -import { AlertTriangle, Loader2, X } from 'lucide-react'; +import { useState } from 'react'; +import { UserPlus } from 'lucide-react'; +import { Modal, Button, Field, Input, Select, Switch, ErrorNote } from '../ui'; /** - * Create a user from the admin console. + * Create a user from the console. * - * The failure that actually happens here is a collision: `createUser` rejects a - * duplicate email *or* student number with one message naming both. That used to - * surface as a toast at the top of the dashboard while the modal closed itself - * and discarded what had been typed. It stays open now and renders the server's - * message next to the form, because re-entering four fields to find out which one - * collided is the whole cost of the mistake. + * The failure that actually happens here is a collision: the server rejects a + * duplicate email with a message naming it. That used to surface as a toast + * while the modal closed and discarded what had been typed. It stays open now + * and renders the server's message beside the form, because re-entering four + * fields to find out which one collided is the whole cost of the mistake. * - * Field limits mirror `SignupRequest`'s column caps. The endpoint takes - * `@RequestBody` without `@Valid`, so nothing else about the shape is enforced - * server-side and nothing else is enforced here either — an operator creating an - * account with an off-pattern address is doing it deliberately. + * Two creation modes, and the default is the safe one. Omitting a password + * creates the account disabled and emails a reset link — nobody handles a + * secret, and the account only becomes usable once its owner sets one. Supplying + * a password creates an account that works immediately, which is occasionally + * what you want (a shared scanner login before an event) and is otherwise a way + * to end up sending a password over Discord. + * + * Field limits mirror the column caps. The endpoint takes `@RequestBody` without + * `@Valid`, so nothing else about the shape is enforced server-side and nothing + * else is enforced here either — an operator creating an account with an + * off-pattern address is doing it deliberately. */ +const LIMITS = { firstName: 20, lastName: 20, email: 50 }; +const EMPTY = { firstName: '', lastName: '', email: '', audience: '', password: '' }; -const LIMITS = { firstName: 20, lastName: 20, email: 50, studentNumber: 10 }; -const EMPTY = { firstName: '', lastName: '', email: '', studentNumber: '' }; - -function Field({ id, label, value, onChange, type = 'text', placeholder, maxLength, autoFocus }) { - return ( -
- - onChange(e.target.value)} - className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" - placeholder={placeholder} - /> -
- ); -} - -function CreateUserModal({ onSubmit, onClose }) { +export default function CreateUserModal({ open, onClose, onSubmit }) { const [form, setForm] = useState(EMPTY); + const [setPasswordNow, setSetPasswordNow] = useState(false); const [error, setError] = useState(null); const [submitting, setSubmitting] = useState(false); - const closeRef = useRef(onClose); - closeRef.current = onClose; - - useEffect(() => { - const onKeyDown = (e) => { - if (e.key === 'Escape') closeRef.current(); - }; - document.addEventListener('keydown', onKeyDown); - return () => document.removeEventListener('keydown', onKeyDown); - }, []); const set = (field) => (value) => { setForm((prev) => ({ ...prev, [field]: value })); setError(null); }; - const complete = Object.values(form).every((v) => v.trim().length > 0); + const complete = + form.firstName.trim() && + form.lastName.trim() && + form.email.trim() && + form.audience && + (!setPasswordNow || form.password.length >= 8); - const submit = async () => { + const close = () => { + setForm(EMPTY); + setSetPasswordNow(false); + setError(null); + onClose(); + }; + + const submit = async (e) => { + e.preventDefault(); if (!complete || submitting) return; setSubmitting(true); const result = await onSubmit({ firstName: form.firstName.trim(), lastName: form.lastName.trim(), email: form.email.trim(), - studentNumber: form.studentNumber.trim(), + audience: form.audience, + ...(setPasswordNow ? { password: form.password } : {}), }); setSubmitting(false); - if (result.ok) onClose(); + if (result.ok) close(); else setError(result.message); }; return ( -
{ - if (e.target === e.currentTarget && !submitting) onClose(); - }} - > -
-
-

Create New User

- + + Create account + + + } + > +
+ {error && {error}} + +
+ + set('firstName')(e.target.value)} + /> + + + set('lastName')(e.target.value)} + /> +
- { - e.preventDefault(); - submit(); - }} - > - - - + - set('email')(e.target.value)} /> + -
-

- Note: The user account will be created in a disabled state. The user must reset their password via the "Forgot Password" link to activate their account. -

+ + + + +
+
+
- {error && ( -
- - {error} + {setPasswordNow && ( +
+ + set('password')(e.target.value)} + /> +
)} +
-
- - -
- -
-
+

+ New accounts carry no grants. Give the account what it needs from the access editor once + it exists. +

+ + ); } - -export default CreateUserModal; diff --git a/src/components/admin/PermissionGrantPanel.jsx b/src/components/admin/PermissionGrantPanel.jsx deleted file mode 100644 index 169e11d..0000000 --- a/src/components/admin/PermissionGrantPanel.jsx +++ /dev/null @@ -1,198 +0,0 @@ -import { useMemo, useState } from 'react'; -import { AlertTriangle, Lock, Minus, Plus, ShieldAlert, X } from 'lucide-react'; -import { - DOMAIN_LABELS, - PERMISSION_DESCRIPTIONS, - PERMISSION_NAMES, - SENSITIVE_PERMISSIONS, -} from '../../utils/permissions'; - -const DOMAIN_ORDER = Object.keys(DOMAIN_LABELS); - -function domainRank(domain) { - const index = DOMAIN_ORDER.indexOf(domain); - return index === -1 ? DOMAIN_ORDER.length : index; -} - -/** - * Direct permission grants for one user. - * - * The distinction this panel exists to draw is effective vs direct. A user holds - * `board:poster:write` either because BOARD_EDITOR carries it or because someone - * granted it to them specifically, and only the second is revocable here — - * without the split an operator tries to revoke a role-derived permission and - * gets an error that reads like a bug. - * - * Rows the current user cannot act on still render when the target holds the - * permission, because "this account has telemetry access and I can't take it - * away" is information the operator needs. What is hidden is the control, not - * the fact. - */ -function PermissionGrantPanel({ targetUser, permissions, heldPermissions, onGrant, onRevoke, onClose }) { - const [pending, setPending] = useState(null); - const [result, setResult] = useState(null); - - const effective = useMemo( - () => new Set(targetUser.effectivePermissions ?? []), - [targetUser.effectivePermissions] - ); - const direct = useMemo( - () => new Set(targetUser.directPermissions ?? []), - [targetUser.directPermissions] - ); - const held = useMemo(() => new Set(heldPermissions), [heldPermissions]); - - const groups = useMemo(() => { - const byDomain = new Map(); - for (const permission of permissions) { - // Only permissions the target already holds or the caller could hand over - // are actionable or informative; the rest would be a wall of dead rows. - if (!effective.has(permission.authority) && !held.has(permission.authority)) continue; - const domain = permission.domain ?? 'other'; - if (!byDomain.has(domain)) byDomain.set(domain, []); - byDomain.get(domain).push(permission); - } - return [...byDomain.entries()].sort(([a], [b]) => domainRank(a) - domainRank(b)); - }, [permissions, effective, held]); - - const roleDerivedCount = effective.size - direct.size; - - const act = async (permission, revoke) => { - const name = permission.name ?? PERMISSION_NAMES[permission.authority]; - setPending(permission.authority); - setResult(await (revoke ? onRevoke(name) : onGrant(name))); - setPending(null); - }; - - return ( -
-
-
-
-

Direct Permissions

-

- {targetUser.firstName} {targetUser.lastName} · {targetUser.email} -

-
- -
- -
-
-
{effective.size}
-
Effective
-
-
-
{roleDerivedCount}
-
From roles
-
-
-
{direct.size}
-
Direct grants
-
-
- - {result && ( -
- {!result.ok && } - {result.message} -
- )} - -
- {groups.length === 0 && ( -

- You hold no permission this user is missing, and they hold none you can see. -

- )} - - {groups.map(([domain, entries]) => ( -
-

- {DOMAIN_LABELS[domain] ?? domain} -

-
- {entries.map((permission) => { - const authority = permission.authority; - const isDirect = direct.has(authority); - const isEffective = effective.has(authority); - const grantable = held.has(authority); - - return ( -
-
-
- {authority} - {isDirect ? ( - - Direct grant - - ) : isEffective ? ( - - From role - - ) : null} - {SENSITIVE_PERMISSIONS.has(authority) && ( - - - Sensitive - - )} -
- {PERMISSION_DESCRIPTIONS[authority] && ( -

- {PERMISSION_DESCRIPTIONS[authority]} -

- )} -
- -
- {isDirect && grantable ? ( - - ) : !isEffective ? ( - - ) : ( - - - {isDirect ? 'You do not hold this' : 'Not revocable here'} - - )} -
-
- ); - })} -
-
- ))} -
-
-
- ); -} - -export default PermissionGrantPanel; diff --git a/src/components/admin/RoleAssignmentModal.jsx b/src/components/admin/RoleAssignmentModal.jsx deleted file mode 100644 index 46aaba0..0000000 --- a/src/components/admin/RoleAssignmentModal.jsx +++ /dev/null @@ -1,163 +0,0 @@ -import { useMemo, useState } from 'react'; -import { AlertTriangle, ShieldAlert, X } from 'lucide-react'; -import { PERMISSION_DESCRIPTIONS, SENSITIVE_PERMISSIONS } from '../../utils/permissions'; -import { bareRoleName } from './roles'; - -/** - * Add or remove one role bundle on one user. - * - * `GET /api/admin/roles` carries each bundle's description and permission set, - * and both are rendered here: "BOARD_ADMIN" on its own tells an operator - * nothing about whether they are handing over the reboot button. - * - * `options` is already filtered by the caller to bundles the current user could - * legitimately assign — the server refuses the rest anyway (§6.5), and offering - * them would only produce a rejection the operator has to interpret. - */ -function RoleAssignmentModal({ mode, targetUser, options, onSubmit, onClose }) { - const [selected, setSelected] = useState(''); - const [result, setResult] = useState(null); - const [submitting, setSubmitting] = useState(false); - - const removing = mode === 'remove'; - const definition = useMemo( - () => options.find((role) => role.name === selected) ?? null, - [options, selected] - ); - - const submit = async () => { - setSubmitting(true); - setResult(await onSubmit(selected)); - setSubmitting(false); - }; - - return ( -
-
-
-

- {removing ? 'Remove Role from User' : 'Add Role to User'} -

- -
- -
-
-

- User: {targetUser.firstName} {targetUser.lastName} -

-

- Email: {targetUser.email} -

-
- - {options.length === 0 ? ( -
- {removing - ? 'This user holds no role you are able to remove. Removing a bundle requires holding every permission in it.' - : 'There is no role you are able to assign. Granting a bundle requires holding every permission in it.'} -
- ) : ( -
- - -
- )} - - {definition && ( -
-

{definition.description}

-
-
- {removing ? 'Permissions this takes away' : 'Permissions this grants'} - {' '}({definition.permissions?.length ?? 0}) -
-
- {(definition.permissions ?? []).map((authority) => ( -
- {SENSITIVE_PERMISSIONS.has(authority) ? ( - - ) : ( - - )} -
- {authority} - {PERMISSION_DESCRIPTIONS[authority] && ( - — {PERMISSION_DESCRIPTIONS[authority]} - )} -
-
- ))} -
-
-
- )} - -
-

- Warning:{' '} - {removing - ? 'The user keeps any of these permissions that another role or a direct grant still provides.' - : 'Every permission listed above becomes effective immediately.'} -

-
- - {result && ( -
- {!result.ok && } - {result.message} -
- )} -
- -
- - -
-
-
- ); -} - -export default RoleAssignmentModal; diff --git a/src/components/admin/RoleBadge.jsx b/src/components/admin/RoleBadge.jsx deleted file mode 100644 index 4e1ad95..0000000 --- a/src/components/admin/RoleBadge.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import { Crown } from 'lucide-react'; -import { bareRoleName, roleTone, ROLE_TONE_CLASSES } from './roles'; - -function RoleBadge({ role }) { - const tone = roleTone(role); - - return ( - - {tone === 'root' && } - {bareRoleName(role)} - - ); -} - -export default RoleBadge; diff --git a/src/components/admin/audit.js b/src/components/admin/audit.js index ceaf69e..ed7852b 100644 --- a/src/components/admin/audit.js +++ b/src/components/admin/audit.js @@ -8,13 +8,19 @@ * one of them is an escalation event. */ -/** Badge classes per tone. */ -export const AUDIT_TONE_CLASSES = { - content: 'bg-blue-100 text-blue-800', - destructive: 'bg-red-100 text-red-800', - fleet: 'bg-indigo-100 text-indigo-800', - access: 'bg-amber-100 text-amber-800', - neutral: 'bg-gray-100 text-gray-700', +/** + * Tone -> the `` tone that renders it. + * + * Four categories, not four decorations. `destructive` is the only one that + * gets a red, and it covers every action that removed something — an upload, a + * role, a device. Scanning the log for red is how you find what went away. + */ +export const AUDIT_TONE_BADGE = { + content: 'cool', + destructive: 'bad', + fleet: 'ember', + access: 'warn', + neutral: 'quiet', }; const AUDIT_ACTION_TONES = { @@ -76,3 +82,17 @@ export const AUDIT_ENTITY_LABELS = { DEVICE: 'Device', EVENT: 'Event', }; + +/** + * `ADD_USER_ROLE` -> `Add user role`. + * + * The enum name is what the server filters on and what an operator will quote + * in a ticket, so the raw value stays available; this is only for the places + * where a row of SHOUTING_CONSTANTS would be the whole visual texture of the + * page. + */ +export function describeAction(action) { + if (!action) return 'Unknown'; + const words = String(action).toLowerCase().split('_'); + return words[0].charAt(0).toUpperCase() + words[0].slice(1) + (words.length > 1 ? ' ' + words.slice(1).join(' ') : ''); +} diff --git a/src/components/admin/index.js b/src/components/admin/index.js index 7b48e81..fa3e6c8 100644 --- a/src/components/admin/index.js +++ b/src/components/admin/index.js @@ -1,6 +1,6 @@ -// Admin console components for the per-permission authorization model. -export { default as AuditActionBadge } from './AuditActionBadge'; +// Access-management components. Everything here is per-permission: the console +// gates on the authority a control needs, never on a role name. +export { default as AccessEditor } from './AccessEditor'; export { default as CreateUserModal } from './CreateUserModal'; -export { default as PermissionGrantPanel } from './PermissionGrantPanel'; -export { default as RoleAssignmentModal } from './RoleAssignmentModal'; -export { default as RoleBadge } from './RoleBadge'; +export * from './audit'; +export * from './roles'; diff --git a/src/components/board/AudienceBadge.jsx b/src/components/board/AudienceBadge.jsx new file mode 100644 index 0000000..8526e03 --- /dev/null +++ b/src/components/board/AudienceBadge.jsx @@ -0,0 +1,20 @@ +import Badge from '../ui/Badge'; +import { AUDIENCE_LABELS } from '../../models/board'; + +/** + * Audience is the single most important attribute of any piece of board + * content — it decides which prayer room sees it — so it gets its own tone per + * value rather than a shared neutral chip. `both` stays quiet: it is the + * default, and the two that need to stand out are the ones that do not go + * everywhere. + */ +const TONES = { brothers: 'cool', sisters: 'ember', both: 'quiet' }; + +export default function AudienceBadge({ audience, size = 'sm' }) { + const key = String(audience ?? 'both').toLowerCase(); + return ( + + {AUDIENCE_LABELS[key] ?? audience} + + ); +} diff --git a/src/components/board/BoardConfigEditor.jsx b/src/components/board/BoardConfigEditor.jsx index 33a838a..bdf4dee 100644 --- a/src/components/board/BoardConfigEditor.jsx +++ b/src/components/board/BoardConfigEditor.jsx @@ -1,5 +1,5 @@ import { memo, useCallback, useState } from 'react'; -import { MapPin, Moon, MessageSquare, Plus, Timer, Trash2 } from 'lucide-react'; +import { Plus, Trash2 } from 'lucide-react'; import { AGENDA_AUTO_RANGE_LABEL, CALCULATION_METHODS, @@ -7,87 +7,48 @@ import { SLIDE_DURATION_MAX_SECONDS, SLIDE_DURATION_MIN_SECONDS, TIMEZONES, - slideDurationErrors + slideDurationErrors, } from '../../models/board'; +import { Button, Field, Input, Select, Switch } from '../ui'; /** - * BoardConfigEditor — edits one device's DeviceConfig, minus the ticker. + * One device's DeviceConfig, minus the ticker. * - * The fields here are exactly the ones UpdateBoardConfigRequest accepts: + * The fields here are exactly the ones `UpdateBoardConfigRequest` accepts: * location, darkModeAfterIsha, agendaDurationSeconds, nextPrayerDurationSeconds. - * Anything else is dropped server-side without an error, so adding a control - * for a field the backend doesn't have produces a setting that silently never + * Anything else is dropped server-side without an error, so adding a control for + * a field the backend does not have produces a setting that silently never * applies — which is how poster-cycle and refresh-after-Isha lingered here. * * `socialUrl` used to be here too, as a "Stay Connected QR" section. It is gone - * from DeviceConfig, from UpdateBoardConfigRequest and from the database. The - * QR destination is no longer one value per board: it lives on each - * PromotableSocialMedia record instead, which is what lets a single board - * promote several accounts. Edit those on the Socials tab. + * from DeviceConfig, from the request DTO and from the database. The QR + * destination is no longer one value per board: it lives on each + * PromotableSocialMedia record, which is what lets a single board promote + * several accounts. Those are edited on the Socials tab. * - * The scrolling ticker moved to TickerEditor below: it is its own sub-resource - * under `board:ticker:write`, which a BOARD_EDITOR holds without holding - * `board:config:write`. Keeping one form would mean one Save the editor could + * The scrolling ticker is `TickerEditor` below: its own sub-resource under + * `board:ticker:write`, which a BOARD_EDITOR holds without holding + * `board:config:write`. One shared form would mean one Save the editor could * never press. * * Fully controlled: the parent owns `config` and receives every edit. */ - -function Toggle({ checked, onChange, label, disabled }) { - return ( - +function BoardConfigEditor({ config, onUpdate, readOnly = false }) { + const setField = useCallback( + (key, value) => { + if (!config || !onUpdate) return; + onUpdate({ ...config, [key]: value }); + }, + [config, onUpdate] ); -} -function Field({ label, hint, error, children }) { - return ( -
- - {children} - {error ? ( -

{error}

- ) : ( - hint &&

{hint}

- )} -
+ const setLocationField = useCallback( + (key, value) => { + if (!config || !onUpdate) return; + onUpdate({ ...config, location: { ...(config.location || {}), [key]: value } }); + }, + [config, onUpdate] ); -} - -const INPUT_BASE_CLASS = - 'w-full rounded-lg border px-3 py-2.5 transition-shadow focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 disabled:bg-gray-50 disabled:text-gray-500'; - -const INPUT_CLASS = `${INPUT_BASE_CLASS} border-gray-200`; - -/** - * The border colour has to be picked, not appended: two `border-*` utilities on - * one element resolve by their order in the stylesheet, not in the attribute, - * so `${INPUT_CLASS} border-red-300` is a coin flip. - */ -const inputClass = (error, extra = '') => - `${INPUT_BASE_CLASS} ${error ? 'border-red-300' : 'border-gray-200'} ${extra}`; - -function BoardConfigEditor({ config, onUpdate, readOnly = false }) { - const setField = useCallback((key, value) => { - if (!config || !onUpdate) return; - onUpdate({ ...config, [key]: value }); - }, [config, onUpdate]); - - const setLocationField = useCallback((key, value) => { - if (!config || !onUpdate) return; - onUpdate({ ...config, location: { ...(config.location || {}), [key]: value } }); - }, [config, onUpdate]); // Turning Auto off has to put *some* number in the box. Remember the one the // admin last typed so a mis-click on the toggle is undoable. @@ -100,6 +61,7 @@ function BoardConfigEditor({ config, onUpdate, readOnly = false }) { if (!config) return null; const location = config.location || {}; + const durationErrors = slideDurationErrors(config); // Latitude/longitude are doubles server-side; an empty input must not become // NaN, so fall back to the previous value while the field is mid-edit. @@ -108,8 +70,6 @@ function BoardConfigEditor({ config, onUpdate, readOnly = false }) { return Number.isNaN(parsed) ? (previous ?? 0) : parsed; }; - const durationErrors = slideDurationErrors(config); - // An emptied box stays empty rather than snapping back to the old number — // `slideDurationErrors` flags it and the parent's Save stays disabled, so the // admin can clear and retype without the field fighting them. @@ -135,173 +95,150 @@ function BoardConfigEditor({ config, onUpdate, readOnly = false }) { }; return ( -
- {/* Location & prayer times */} -
-
- -

Location & Prayer Times

-
-
-

- The board computes prayer times itself from these coordinates — there is no - server-side prayer source, so a wrong timezone here shows wrong times on screen. -

- -
- - setLocationField('city', e.target.value)} - disabled={readOnly} - className={INPUT_CLASS} - placeholder="Mississauga" - /> - - - setLocationField('country', e.target.value)} - disabled={readOnly} - className={INPUT_CLASS} - placeholder="Canada" - /> - -
- -
- - setLocationField('latitude', numeric(e.target.value, location.latitude))} - disabled={readOnly} - className={INPUT_CLASS} - /> - - - setLocationField('longitude', numeric(e.target.value, location.longitude))} - disabled={readOnly} - className={INPUT_CLASS} - /> - -
- -
- - - - - - -
+
+
+

Location & prayer times

+

+ The board computes prayer times itself from these coordinates. There is no server-side + prayer source, so a wrong timezone here puts wrong times on the screen and nothing else + will catch it. +

+ +
+ + setLocationField('city', e.target.value)} + /> + + + setLocationField('country', e.target.value)} + /> + + + + setLocationField('latitude', numeric(e.target.value, location.latitude)) + } + /> + + + + setLocationField('longitude', numeric(e.target.value, location.longitude)) + } + /> + + + + + + +
- {/* Night mode */} -
-
-
- -
-

Night Mode

-

Dims the board after Isha

-
-
- setField('darkModeAfterIsha', v)} - label="Enable night mode after Isha" - disabled={readOnly} - /> -
+
+

Night mode

+ setField('darkModeAfterIsha', v)} + disabled={readOnly} + label="Dim the board after Isha" + hint="The screen sits in a prayer room overnight. This drops it to the dark theme rather than leaving a bright panel lighting the space." + />
- {/* Slide timing */} -
-
- -
-

Slide Timing

-

How long the board holds each of these slides

-
-
-
+
+

Slide timing

+

+ How long the board holds each of these slides. Between{' '} + {SLIDE_DURATION_MIN_SECONDS} and {SLIDE_DURATION_MAX_SECONDS} seconds. +

+ +
Agenda slide duration (seconds)} + label="Agenda slide (seconds)" error={durationErrors.agendaDurationSeconds} hint={ agendaAuto - ? `Auto - ${AGENDA_AUTO_RANGE_LABEL}.` + ? `Auto — ${AGENDA_AUTO_RANGE_LABEL}.` : 'The agenda holds this long however many events it lists.' } > -
- + setDuration('agendaDurationSeconds', e.target.value)} - disabled={readOnly || agendaAuto} placeholder={agendaAuto ? 'Auto' : ''} - className={inputClass(durationErrors.agendaDurationSeconds, 'min-w-[6rem] flex-1')} + error={durationErrors.agendaDurationSeconds} + disabled={readOnly || agendaAuto} + onChange={(e) => setDuration('agendaDurationSeconds', e.target.value)} + /> + -
- - Auto -
Next prayer slide duration (seconds)} + label="Next prayer slide (seconds)" error={durationErrors.nextPrayerDurationSeconds} hint="How long the countdown to the next prayer stays up." > - setDuration('nextPrayerDurationSeconds', e.target.value)} + error={durationErrors.nextPrayerDurationSeconds} disabled={readOnly} - placeholder="12" - className={inputClass(durationErrors.nextPrayerDurationSeconds)} + onChange={(e) => setDuration('nextPrayerDurationSeconds', e.target.value)} />
@@ -311,92 +248,90 @@ function BoardConfigEditor({ config, onUpdate, readOnly = false }) { } /** - * TickerEditor — the `scrollingMessages` / `enableScrollingMessage` pair. + * The `scrollingMessages` / `enableScrollingMessage` pair. * - * Lives here rather than in its own file so it keeps sharing the Toggle and - * input styling with the config form it used to be part of; it is a separate - * card with a separate Save because it is a separate permission. + * Lives in this file rather than its own so it keeps sharing the config form's + * shape and vocabulary; it is a separate panel with a separate Save because it + * is a separate permission. */ function TickerEditor({ config, onUpdate, readOnly = false }) { - const setField = useCallback((key, value) => { - if (!config || !onUpdate) return; - onUpdate({ ...config, [key]: value }); - }, [config, onUpdate]); - - const setMessage = useCallback((index, value) => { - const messages = [...(config.scrollingMessages || [])]; - messages[index] = value; - setField('scrollingMessages', messages); - }, [config, setField]); + const setField = useCallback( + (key, value) => { + if (!config || !onUpdate) return; + onUpdate({ ...config, [key]: value }); + }, + [config, onUpdate] + ); - const removeMessage = useCallback((index) => { - setField('scrollingMessages', (config.scrollingMessages || []).filter((_, i) => i !== index)); - }, [config, setField]); + const setMessage = useCallback( + (index, value) => { + const messages = [...(config.scrollingMessages || [])]; + messages[index] = value; + setField('scrollingMessages', messages); + }, + [config, setField] + ); if (!config) return null; const messages = config.scrollingMessages || []; return ( -
-
-
- -
-

Scrolling Messages

-

Cycled in the ticker along the bottom

-
-
- setField('enableScrollingMessage', v)} - label="Enable scrolling messages" - disabled={readOnly} - /> -
+
+ setField('enableScrollingMessage', v)} + disabled={readOnly} + label="Show the ticker" + hint="Messages cycle along the bottom of this board." + /> {config.enableScrollingMessage && ( -
+
{messages.length === 0 && ( -

+

No messages — the ticker stays hidden until you add one.

)} + {messages.map((message, index) => (
- setMessage(index, e.target.value)} + placeholder="Something worth reading twice" disabled={readOnly} - className={`${INPUT_CLASS} flex-1`} - placeholder="Enter message..." + onChange={(e) => setMessage(index, e.target.value)} /> {!readOnly && ( - + className="shrink-0 text-faint hover:text-bad" + onClick={() => + setField( + 'scrollingMessages', + messages.filter((_, i) => i !== index) + ) + } + /> )}
))} + {!readOnly && ( - + Add message + )}
)} -
+
); } diff --git a/src/components/board/EventsEditor.jsx b/src/components/board/EventsEditor.jsx index aaea197..53fbce6 100644 --- a/src/components/board/EventsEditor.jsx +++ b/src/components/board/EventsEditor.jsx @@ -1,406 +1,595 @@ -import { useState, memo } from 'react'; +import { useMemo, useState } from 'react'; import { - Plus, Trash2, Edit2, Check, X, Calendar, Clock, - MapPin, Users, Search, CalendarDays + CalendarClock, + CalendarDays, + Clock, + ClipboardCheck, + Edit2, + FileText, + ImagePlus, + Link2Off, + MapPin, + Plus, + Ticket, + Trash2, } from 'lucide-react'; import BoardService from '../../services/BoardService'; +import TicketEventPicker from './TicketEventPicker'; +import AudienceBadge from './AudienceBadge'; +import { AUDIENCE_LABELS, AUDIENCE_OPTIONS } from '../../models/board'; import { useAuth } from '../../context/AuthContext'; import { PERMISSIONS } from '../../utils/permissions'; +import useWizard from '../../hooks/useWizard'; +import { + Badge, + Button, + Checkbox, + ConfirmDialog, + EmptyState, + Field, + Input, + KeyValue, + SearchInput, + SegmentedControl, + Select, + StepIntro, + Textarea, + WizardShell, + useToast, +} from '../ui'; -const audienceOptions = [ - { value: 'both', label: 'Everyone', color: 'bg-purple-100 text-purple-700', border: 'border-purple-200' }, - { value: 'brothers', label: 'Brothers', color: 'bg-blue-100 text-blue-700', border: 'border-blue-200' }, - { value: 'sisters', label: 'Sisters', color: 'bg-pink-100 text-pink-700', border: 'border-pink-200' } -]; - -const formatDateTimeLocal = (timestamp) => { - const date = new Date(timestamp); - return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}T${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`; +/** `` wants local wall-clock, not an ISO instant. */ +const toLocalInput = (epochMs) => { + const d = new Date(epochMs); + const pad = (n) => String(n).padStart(2, '0'); + return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`; }; -function EventForm({ isNew, formData, setFormData, onCancel, onSave }) { - return ( -
-
-

{isNew ? 'New Event' : 'Edit Event'}

- -
+const fmtWhen = (epochMs) => + new Date(epochMs).toLocaleString('en-US', { + weekday: 'short', + month: 'short', + day: 'numeric', + hour: 'numeric', + minute: '2-digit', + }); -
-
- - setFormData({ ...formData, name: e.target.value })} - className="w-full px-3 py-2.5 border border-gray-200 rounded-lg focus:ring-2 focus:ring-indigo-500 bg-white" - placeholder="e.g., Weekly Halaqa" - /> -
- -
- - setFormData({ ...formData, startEpochMs: new Date(e.target.value).getTime() })} - className="w-full px-3 py-2.5 border border-gray-200 rounded-lg focus:ring-2 focus:ring-indigo-500 bg-white" - /> -
- -
- - setFormData({ ...formData, endEpochMs: new Date(e.target.value).getTime() })} - className="w-full px-3 py-2.5 border border-gray-200 rounded-lg focus:ring-2 focus:ring-indigo-500 bg-white" - /> -
- -
- - setFormData({ ...formData, location: e.target.value })} - className="w-full px-3 py-2.5 border border-gray-200 rounded-lg focus:ring-2 focus:ring-indigo-500 bg-white" - placeholder="e.g., Room 2080" - /> -
- -
- -
- {audienceOptions.map(opt => ( - - ))} -
-
- -
- -